一句話總結:從問題現象爲入口,歸結爲3類問題進行定位分析:內存滿、CPU高、線程阻塞。
首先先介紹下jvisualvm這款jdk自帶的性能工具。通常我們要定位哪塊代碼性能差,耗時久,最原始的辦法就是在各個方法前後日誌打印時間戳並計算耗時,這種方法很繁瑣,通常要加很多日誌多次部署才能定位到,我一開始也是這麼搞的。而使用jvisualvm工具則可以直接查看整個業務代碼調用鏈中各個方法的耗時及佔比,直接就能定界出是哪個方法性能差,耗時久。
操作步驟:點擊抽樣器頁籤,點擊CPU抽樣,前臺操作觸發代碼執行,操作完點擊停止,再點擊快照。在快照頁點擊搜索按鈕,輸入代碼入口方法名搜索,結果見下圖。
我們來分析下下圖,RegionNorthbound.createRegion是北向入口方法,總耗時見右側5.298秒,其調用了RegionServiceImpl.createRegion方法,耗時4.093秒,那麼相減就是RegionNorthbound.createRegion方法本身的耗時。
RegionServiceImpl.createRegion方法調用了CommonDao.saveEntity和RegionServiceImpl.checkRepeat方法,分別耗時0.998秒和0.094秒。可以直觀查看是調用的哪個方法耗時久,性能差。整個方法調用鏈的耗時均可查看。
這在大型系統中非常有用。大型系統中通常業務由業務部門開發,平臺由平臺部門開發,只提供jar包給業務部門使用。那麼當測試出性能問題時,首先要定界出是業務的問題還是平臺的問題,再轉給對應部門去修改。業務開發不知道平臺的實現,無法查看修改其代碼,更不用說去加日誌。固通過此工具可以快速定界出問題責任主體。
當然jvisualvm除了查看代碼調用鏈耗時,實時監控CPU、內存、線程數,線程dump、內存dump等功能都非常好用。
下面開始按問題分類進行定位分析。
一:內存問題
問題現象:後臺報錯,前臺提示異常或500錯誤。
定位:後臺報錯的定位思路很明確了,查看日誌,這裏分兩類異常:一、java.lang.OutOfMemoryError: PermGen space異常,此爲方法區內存溢出,方法區用來存放class代碼,通常解決辦法就是調大jvm permSize參數值;二、java.lang.OutOfMemoryError: heap space異常,比較常見的堆內存溢出。當然也可以調大jvm參數來解決,但若是不恰當代碼引起的,首先檢查下自己寫的代碼,看哪裏創建了大量對象。若檢查不出來,則使用jmap或jvisualvm導出內存快照。
分析下圖heap dump,這裏又分兩種情況,若左側佔內存大的類名爲com.**.User這種自己定義的業務對象,那在代碼中搜索下此類名就能定位到哪裏創建了大量此對象。若爲String、Integer這種基本類型的對象,則可以使用Eclipse Memory Analyer此類工具進一步分析,可以查看內存大對象的線程堆棧,即可查看代碼調用方法。
二:CPU問題
問題現象:前臺顯示卡頓,響應時間長,linux top查看cpu使用率非常高
定位步驟:
1、使用top查看進程pid,java系統通常進程名就叫java
2、使用top -H -p pid查看進程中各線程的CPU使用情況,找出最佔CPU的線程pid,注意這裏其實就是tid
3、通過步驟2我們知道是哪個線程佔CPU了,使用jstack pid打印線程堆棧,查看該線程的代碼調用方法
4、將步驟2線程tid轉換爲16進制,因爲jstack打印出來的threaddump中tid爲16進制,然後在threaddump中搜索,即可找到線程堆棧,這裏30725轉換爲16進制就是7805
這裏有個情況要特別說明,如果步驟4查看的佔用CPU的線程爲java gc線程,通常是由於內存快滿才導致jvm頻繁gc,進而導致CPU高。固此種情況得按上面提到的內存問題去定位解決。Java架構交流學習圈:874811168 面向1-3年經驗 Java開發人員 幫助突破瓶頸 提升思維能力
三:線程問題
問題現象1:請求響應時間長,性能測試TPS/QPS上不去,查看CPU佔用又不高
問題現象2:請求響應直接超時,後臺線程相互死鎖
定位:線程問題通常打印一次threaddump是看不出問題的,要多打印幾次對比才能看出問題。建議使用jvisualvm線程頁籤實時查看線程狀態。查看指定業務線程狀態,若長時間處於wait或block狀態,則可確認該問題是由於線程阻塞引起的。查看線程堆棧可查看是調用哪個方法時阻塞的。死鎖問題也是類似定位,線程堆棧裏會提示在等待哪個線程釋放lock,而有兩個線程互相等待即會死鎖。
備忘:
jvisualvm連接遠程jvm,遠程jvm添加啓動參數:
-Djava.rmi.server.hostname=10.6.188.43 -Dcom.sun.management.jmxremote.port=18888 -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.managementote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false