分析前提:
- mat的安裝詳情:https://blog.csdn.net/qq_34599254/article/details/82685110
https://www.cnblogs.com/trust-freedom/p/6744948.html - 安裝後,再具體使用過程中,發現我dump的文件有4個G,而mat默認的配置是1個G,因此需要修改默認配置,修改流程如下:https://blog.csdn.net/freshfishfish/article/details/81585369
- jps命令查詢進程號
-
jmap -histo pid | head -n20 查看TOP20對象分佈,這個直接展示在屏幕上,也可打印到文件 jmap -histo:live [pid] >a.log
-
jstat -gc pid 3000:每隔三秒打印一次gc信息,運行結果中:
O:old代已使用的佔當前容量百分比 ,YGC:從應用程序啓動到採樣時年輕代中gc次數
YGCT:從應用程序啓動到採樣時年輕代中gc所用時間(s)
FGC:從應用程序啓動到採樣時old代(全gc)gc次數
FGCT:從應用程序啓動到採樣時old代(全gc)gc所用時間(s)
GCT:從應用程序啓動到採樣時gc用的總時間(s)
-
jmap -dump:format=b,file=/home/admin/logs/heap.hprof pid dump出系統的堆棧詳情,便於用mat工具分析
-
匿名內部類的含義
-
Spring創建的實例,默認都是單例的
真實的代碼實例不方便展示給大家,這裏寫一個簡單的demo,方便大家感知下使用了內部類的一些問題:
//定義一個Sping的服務,有個test方法,作用就是往list裏面add線程
@Component
public class MonitorServiceImpl implements MonitorService {
private static List<Runnable> list = new ArrayList<>();
@Override public void test(final GnssDeviceListInfo info) {
list.add(new Runnable() {
@Override public void run() {
info.getDeviceType();
}
});
}
}
//test方法就是往List隊列中加線程任務(new Runnable這種寫法就是匿名內部類)
//模擬一個不斷調用該服務方法的restful接口,方便用Postman調用:
@GetMapping("/test")
public queryAllInServiceDevice(){
while(true){
Thread.sleep(1);
monitorService.test(new GnssDeviceListInfo());
}
}
項目啓動後調用/test,就會觸發test服務方法,運行一段時間後,發現系統非正常運行了,可以有很多指標,這裏不說了,我們直接分析:
第0步:jps命名找服務進程號
第一步:觀測GC信息
執行命名jstat -gccause 24203 3000,觀測gc,發現:
第二步:用jmap命令打印出前30個耗費資源最多的對象:
這個例子寫的太簡單,直接可以看出來,假設我們看不出來,需要使用mat工具來排查,需要先dump實時的堆棧信息:
第三步:獲取dump堆棧:jmap -dump:format=b,file=/Users/chao.zheng/heap.hprof 24203
第四步:根據mat分析生成的heap.hprof 文件:
分析第一個problem suspect 1
點擊這個案例,用樹形結構展示,方便看:
發現MonitorServiceImpl創建了大量的實例(其實上面的jmap -histo命令直接就可以看出來了(這裏只是爲了演示一些操作,讀者可不必在意啊),有接近100萬個對象創建出來了,這不是與大家掌握的知識:Spring默認是單例的有衝突嘛)
分析總結:
- Spring中的服務默認是單例的,這句話無可厚非
- 例子中的list.add(new Runnable()),會不斷的創建線程任務到list中,因爲寫法是new Runnable(),實際就會創建一個匿名內部類(如何判斷的呢,MonitorServiceImpl$1,看這個字符串末尾的$1就代表是內部類的標誌)
- 如果沒有線程及時的去處理list中的任務,就會導致list中的任務越來越多,如果還沒有限制List的容量大小,最終會導致佔用的內存越來越多,觀察gc信息也可以發現,fullgc越來越頻繁,系統幾乎一直在fullgc,導致系統幾乎Hung死
由於示範的例子不是很好,可能無法很好的去描述問題,有疑問歡迎留言~
或者直接點擊這個按鈕:
可能這個對象最多的不一定佔用內存最大,但我們知道java是引用的,可能他引用的對象超級多、大,可以進一步慢慢分析