一、內存泄漏
1、堆內存溢出
現象:
(1)壓測執行一段時間后,系統處理能力下降。這時用JConsole、JVisualVM等工具連上服務器查看GC情況,每次GC回收都不徹底並且可用堆內存越來越少。
(2)壓測持續下去,最終在日志中有報錯信息:java.lang.OutOfMemoryError.Java heap space。
排查手段:
(1)使用jmap -histo pid > test.txt命令將堆內存使用情況保存到test.txt文件中,打開文件查看排在前50的類中有沒有熟悉的或者是公司標注的類名,如果有則高度懷疑內存泄漏是這個類導致的。
(2)如果沒有,則使用命令:jmap -dump:live,format=b,file=test.dump pid生成test.dump文件,然后使用MAT進行分析。
(3)如果懷疑是內存泄漏,也可以使用JProfiler連上服務器在開始跑壓測,運行一段時間后點擊“Mark Current Values”,后續的運行就會顯示增量,這時執行一下GC,觀察哪個類沒有徹底回收,基本就可以判斷是這個類導致的內存泄漏。
解決方式:優化代碼,對象使用完畢,需要置成null。
2、持久代溢出
現象:壓測執行一段時間后,日志中有報錯信息:java.lang.OutOfMemoryError: PermGen space。
產生原因:由於類、方法描述、字段描述、常量池、訪問修飾符等一些靜態變量太多,將持久代占滿導致持久代溢出。
解決方法:修改JVM參數,將XX:MaxPermSize參數調大。盡量減少靜態變量。
3、棧內存溢出
現象:壓測執行一段時間后,日志中有報錯信息:java.lang.StackOverflowError。
產生原因:線程請求的棧深度大於虛擬機所允許的最大深度,遞歸沒返回,戒者循環調用造成。
解決方法:修改JVM參數,將Xss參數改大,增加棧內存。棧內存溢出一定是做批量操作引起的,減少批處理數據量。
4、系統內存溢出
現象:壓測執行一段時間后,日志中有報錯信息:java.lang.OutOfMemoryError: unable to create new native thread。
產生原因:操作系統沒有足夠的資源來產生返個線程造成的。系統創建線程時,除了要在Java堆中分配內存外,操作系統本身也需要分配資源來創建線程。因此,當線程數量大到一定程度以后,堆中或許還有空間,但是操作系統分配不出資源來了,就出現這個異常了。
解決方法:
(1)減少堆內存
(2)減少線程數量
(3)如果線程數量不能減少,則減少每個線程的堆棧大小,通過-Xss減小單個線程大小,以便能生產更多的線程。
5、JAVA直接內存溢出
現象:壓測執行一段時間后,日志中有報錯信息:OutOfMemoryError
產生原因:
(1)直接內存大多時候也被稱為堆外內存,直接內存通過 native 方法可以分配堆外內存,通過 DirectByteBuffer 對象來操作。直接內存不屬於 Java 堆,所以它不受堆內存大小限制,但是它受物理內存大小的限制。
(2)可以通過 -XX:MaxDirectMemorySize 參數來設置最大可用直接內存,如果啟動時未設置則默認為最大堆內存大小,即與 -Xmx 相同。即假如最大堆內存為1G,則默認直接內存也為1G,那么 JVM 最大需要的內存大小為2G多一些。當直接內存達到最大限制時就會觸發GC,如果回收失敗則會引起OutOfMemoryError。
(3)直接內存在讀和寫的性能都優於堆內內存,但是內存申請速度卻不如堆內內存。
解決方法:因此直接內存適用於需要大內存空間且頻繁訪問的場合,不適用於頻繁申請釋放內存的場合。在需要頻繁申請的場景下不應該使用直接內存(DirectMemory),而應該使用堆內內存(HeapMemory)。
二、CPU過高
1、us cpu高
現象:壓測過程中,使用top命令查看系統資源占用情況,us cpu過高,超過50%以上。
排查手段:
(1)使用top命令是哪個進程消耗CPU高
(2)再找到CPU消耗高的線程:top -H -p 進程號
(3)把線程號轉換成16進制:printf "%x\n" 線程號
(4)再用jstack命令分析這個線程是在干什么:jstack 進程號 | grep 16進制的線程號
(5)通過JProfiler的CPU Views視圖的層層分析,可以清楚的找到造成CPU高的原因
2、sy cpu高
現象:壓測過程中,使用top命令查看系統資源占用情況,sy cpu過高,超過50%以上。
排查手段:
(1)首先查看磁盤繁忙程度、磁盤的隊列(iostat、nmon)
(2)如果磁盤沒有問題,則使用strace查看系統內核調用情況
三、線程死鎖
現象:
(1)壓測進行一段時間后,程序停頓,報超時錯誤。但這種現象並不一定就是線程死鎖造成的,也可能是數據庫/中間件連接池被占滿、數據庫死鎖造成的。
(2)能夠打開頁面,但獲取不到數據
排查手段:
(1)使用jstack命令查看Java進程下所有線程的情況:jstack -l 進程號
(2)如果有Blocked狀態的線程,說明有線程死鎖的狀況。如果大量線程都是Waiting狀態,則需要去關注數據庫和中間件,可能會有排隊情況。
(3)也可以使用JConsole、JVisualVM及JProfiler等工具直接查看所有線程的情況
四、數據庫連接池不釋放
現象:壓測進行一段時間后,報連接超時的錯誤。
排查手段:
(1)去數據庫查看應用程序到數據庫的連接有多少個:show full processlist。加入應用程序中配置的最大連接數為30,而通過命令show full processlist查看到的從應用服務器連接過來的連接數也為30,證明數據庫連接池占滿了。
(2)將應用程序中的最大連接數改大一點(比如100),再重新進行壓測,如果還是出現連接池被占滿的情況,則證明是數據庫連接池不釋放造成的.
解決方法:排查代碼,數據庫連接部分應該是有創建連接但是沒有關閉連接的情況。讓開發修改代碼即可。
五、數據庫死鎖
現象:
(1)壓測進行一段時間后,報連接超時的錯誤。
(2)程序在執行的過程中,點擊確定或保存按鈕,程序沒有響應,也沒有出現報錯。
排查手段:查看數據庫日志,看有沒有死鎖的情況:show engine innodb status\G。
六、SQL使用不合理
現象:事物響應時間慢
排查手段:
(1)打開數據庫的慢查詢
(2)通過命令找到執行比較久的SQL語句:mysqldumpslow
(3)利用explain來優化這條SQL語句
七、TPS上不去
1、網絡帶寬
在壓力測試中,有時候要模擬大量的用戶請求,如果單位時間內傳遞的數據包過大,超過了帶寬的傳輸能力,那么就會造成網絡資源競爭,間接導致服務端接收到的請求數達不到服務端的處理能力上限。
2、連接池
最大連接數太少,造成請求等待。連接池一般分為服務器中間件連接池(比如Tomcat)和數據庫連接池(或者理解為最大允許連接數也行)。
3、垃圾回收機制
從常見的應用服務器來說,比如Tomcat,如果堆內存設置比較小,就會造成新生代的Eden區頻繁的進行Young GC,老年代的Full GC也回收較頻繁,那么對TPS也是有一定影響的,因為垃圾回收時通常會暫停所有線程的工作。
4、數據庫
高並發情況下,如果請求數據需要寫入數據庫,且需要寫入多個表的時候,如果數據庫的最大連接數不夠,或者寫入數據的SQL沒有索引沒有綁定變量,抑或沒有主從分離、讀寫分離等,就會導致數據庫事務處理過慢,影響到TPS。
5、硬件資源
包括CPU(配置、使用率等)、內存(占用率等)、磁盤(I/O、頁交換等)。
6、壓力機
比如Jmeter和Loadrunner,單機負載能力有限,如果需要模擬的用戶請求數超過其負載極限,也會間接影響TPS(這個時候就需要進行分布式壓測來解決其單機負載的問題)。
7、業務邏輯
業務解耦度較低,較為復雜,整個事務處理線被拉長也會導致TPS上不去。
8、系統架構
比如是否有緩存服務,緩存服務器配置,緩存命中率、緩存穿透以及緩存過期等,都會影響到測試結果。
八、500或503錯誤
現象:壓測過程中,服務器返回500或503錯誤
排查手段
(1)通過瀏覽器訪問網站,如果瀏覽器打不開,服務器可能掛了。可能的原因是:內存溢出、數據庫連接池滿了、線程死鎖。
(2)先看JVM內存是否滿了:jstat -gcutil 2384 1000 5
(3)看數據庫連接池大小是否占滿,把最大連接數改大,如果還是出現這種現象,證明是數據庫連接池不釋放。
(4)最后看是否有線程死鎖:jstack -l 進程號
九、性能問題分析流程
1、查看服務器的CPU、內存 、負載等情況,包括應用服務器和數據庫服務器
2、查看數據庫健康狀態,數據庫死鎖、連接池不釋放
3、查看項目日志(查看無報錯現象)
4、查看jvm的gc等情況