Jmap+MAT 排查內存泄漏

最近在項目中自測的時候(壓力測試)遇到內存泄漏的情況,想查看具體是哪個模塊甚至哪個類引起的問題,經同事推薦使用Jmap+MAT的組合進行了一次嘗試,下面記錄一下,以便今後深入學習使用。

在程序出現內存溢出情況之前,想要觀察內存使用情況的話,可以藉助atop命令查看內存的使用情況。

這裏可以用一個linux下的命令(jps:虛擬機進程狀況工具)查看所有Java相關線程的pid等信息

然後使用jmap(Java內存映像工具)命令,jmap是一個可以輸出內存中所有對象的工具,甚至可以將VM中的heap,以二進制輸出成文本。jmap-dump:format=b,file=heap.bin 8120可以將8120進程的內存heap輸出到heap.bin文件裏。它可以打印出某個Java進程(使用pid)內存中所有“對象”的情況(如:產生哪些對象,及其數量)。

再借助MAT(將heap.bin導入到MAT中)生成內存消耗的詳細信息。

MAT安裝與介紹
下載地址:http://www.eclipse.org/mat/downloads.php。
通過MAT打開dump出來的內存文件,打開後如下圖:




從上圖可以看到它的大部分功能。
1. Histogram可以列出內存中的對象,對象的個數以及大小。
2. Dominator Tree可以列出那個線程,以及線程下面的那些對象佔用的空間。
3.Top consumers通過圖形列出最大的object。
4.Leak Suspects通過MA自動分析泄漏的原因。
Histogram如下圖:
Objects:類的對象的數量。
Shallow size:就是對象本身佔用內存的大小,不包含對其他對象的引用,也就是對象頭加成員變量(不是成員變量的值)的總和。
Retained size:是該對象自己的shallow size,加上從該對象能直接或間接訪問到對象的shallow size之和。換句話說,retained size是該對象被GC之後所能回收到內存的總和。
我們發現ThreadLocal和bingo.persister.dao.Daos類的對象佔用了很多空間。



Dominator Tree如下圖:
我們發現quartz的定時器的工作線程(10個)佔了很多的內存空間




Top consumers如下圖:
這裏顯示了內存中最大的對象有哪些,他們對應的類是哪些,類加載器classloader是哪些。
有些時候,我們在這裏就可以看到代碼泄露的位置。




Leak Suspects如下圖:
從那個餅圖,該圖深色區域被懷疑有內存泄漏,可以發現整個heap才250M內存,深色區域就佔了34%。後面的描述,告訴我們quartz線程佔用了大量內存,並指出system class loader加載的"java.lang.ThreadLocal"實例的內存中聚集(消耗空間),並建議用關鍵字"java.lang.ThreadLocal$ThreadLocalMap$Entry[]"進行檢查。所以,MAT通過簡單的報告就說明了問題所在。




通過Leak Suspects的Problem Suspect 1點擊【Details »】,
如下圖如下圖所示的上下文菜單中選擇 List objects -> with outgoning references, 查看ThreadLocal都應用了些什麼對象。





現在看到ThreadLocal中引用的對象如下圖:
是dao對象
ps:該dao對象包含一個輕量級的ORM關係內容,所以Retained size比較大



下面繼續查看dao的gc ROOT
如下圖所示的上下文菜單中選擇 Path To GC Roots -> exclude weak references, 過濾掉弱引用,因爲在這裏弱引用不是引起問題的關鍵。




從下圖中,可以看到在org.quartz.simpl.SimpleThreadPool中保存了daos的引用。所以可以得出是是因爲定時器在運行的過程中持有大量的Daos對象應起了內存泄露。爲什麼會有那麼多的Daos呢,Daos不是一個無狀態的單例的、可以重用的嗎?繼續查看spring配置文件發現Daos的bean配置成scope="prototype",導致定時任務又是每次調用都生產新的Daos實例。由於是Daos是無狀態的,修改爲單例的,問題解決。




以上是通過MAT分析Tomcat應用程序,找到內存泄露的原因,並解決。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章