排查Java內存泄露實戰(內附各種排查工具介紹)

今天剛剛纔加一個故障review會議, 故障非常典型, google下也可以找到相似案例介紹。 在排查問題的過程中,使用了大量的工具, 發現有問題的地方還不只一個,總結一下. (本篇文章不會重點描述案例本身,重點會介紹個人對java內存泄露問題的排查思路和各種工具的使用)。

java內存泄露典型特徵

現象一:堆/Perm 區不斷增長, 沒有下降趨勢(回收速度趕不上增長速度), 最後不斷觸發FullGC, 甚至crash(如下兩張圖是同一個應用的GC和Perm數據, GC觸發原因確認是Perm不足). 一般是現象二的晚期表現。

現象二:每次FullGC後, 堆/Perm 區在慢慢的增長, 最後不斷觸發FullGC, 甚至crash(如下圖: 示意圖)

java內存泄露場景---PermGen space

原因:說明Perm不足. Perm存放class,method相關對象,以及運行時常量對象. 如果一個應用加載了大量的class, 那麼Perm區存儲的信息一般會比較大.另外大量的intern String對象也會導致Perm區不斷增長。此區域大小由-XX:MaxPermSize參數進行設置. (jdk8相關參數已經改變, 這裏不討論)

案例: Groovy動態編譯class, xstream String.intern

本質原因:ClassLoader.defineClass和java.lang.String.intern在大量不適宜的場景被調用.

解決方案:

方案1(直接有效): 使用btrace相關工具輸出調用ClassLoader.defineClass棧信息, 從棧信息來追溯問題. (代碼如下圖). 但Btrace 不能trace jvm native方法(官方release btrace 1.3.1中版本聲明可以trace native方法, 但嘗試無效。如果你清楚如何使用,麻煩告知一下,謝謝)。

用JProfiler來trace String.intern方法棧

方案2: dump heap, 看看哪些class有異常現象(數量), String被Perm區引用的對象信息等.但這種方式不太直觀,可以從String數據看看發現可疑問題,沒有方案1直觀。(如下圖: 如果能在日常調試推薦JProfiler)

方案3: 增加-XX:+TraceClassLoading和-XX:+TraceClassUnloading, 看看哪些class加載了,哪些class卸載了. 如果一些特殊的class一直被加載而沒有被卸載說明也是有問題的。(如下圖)

方案4:執行jmap -permgen(jstat -gcutil 可以查看內存增長速度和區域)命令看看Perm區中的內容, 初步確定是否存在問題 (如下圖)

java內存泄露場景---Java heap space

原因:長生命週期的對象引用了短生命週期(應該儘快GC回收掉)的對象,最後造成一個對象已經不能在堆區分配足夠空間. 注: 這種現象不能完全肯定是內存泄露, 比如: heap本身的設置的過小.

案例: 我個人沒有遇到過這種案例, 但模擬過這種情形的Demo: 參考我的《深入淺出JProfiler》文章, 也學習過其他同學的案例: 例如:參數過大並且頻繁超時導致內存泄露

解決方案:

觸發FullGC, dump live heap. 標記堆中對象數量, 重點關注可疑對象

觸發FullGC, dump live heap. 標記堆中對象數量, 重點關注可疑對象

對比步驟1和步驟2 相同對象的數量和大小, 找出可疑對象一一進行排查確認。

如果步驟3依然無法明確有問題的對象, 那就多執行幾次步驟1和步驟2。在此過程中可以調整GC觸發時間, 模擬真實的故障場景 :)

看看GC後堆的大小是否增長, 如果沒有不斷增長, 並且持續一段較長時間, 那基本正常。

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