如何排查Java記憶體洩露(內附各種排查工具介紹)

不聞發表於2016-09-30

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

java記憶體洩露典型特徵

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

_2016_09_28_11_01_16
_2016_09_28_11_01_05

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

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方法, 但嘗試無效。如果你清楚如何使用,麻煩告知一下,謝謝).
 ![_2016_09_29_12_59_38](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/c5bbdd783fb0606a9817c8296babce42)

    * **用JProfiler來trace String.intern方法棧
* 方案2: dump heap, 看看哪些class有異常現象(數量), String被Perm區引用的物件資訊等.但這種方式不太直觀,可以從String資料看看發現可疑問題,沒有方案1直觀。(如下圖: 如果能在日常除錯推薦JProfiler**)
![_2016_09_29_1_46_59](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/8e5d32f29e6166695ea976ae8f9ba89d)
* 方案3: 增加-XX:+TraceClassLoading和-XX:+TraceClassUnloading, 看看哪些class載入了,哪些class解除安裝了. 如果一些特殊的class一直被載入而沒有被解除安裝說明也是有問題的。(如下圖)

![_2016_09_29_1_06_46](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/b826a15b81570c02590fda7e598100cd)
* 方案4:執行jmap -permgen(jstat -gcutil  可以檢視記憶體增長速度和區域)命令看看Perm區中的內容, 初步確定是否存在問題 (如下圖)

_2016_09_29_3_07_42

_2016_09_29_1_45_46

java記憶體洩露場景—Java heap space

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

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

    1. 觸發FullGC, dump live heap. 標記堆中物件數量, 重點關注可疑物件
    2. 觸發FullGC, dump live heap. 標記堆中物件數量, 重點關注可疑物件
    3. 對比步驟1和步驟2 相同物件的數量和大小, 找出可疑物件一一進行排查確認。
    4. 如果步驟3依然無法明確有問題的物件, 那就多執行幾次步驟1和步驟2。 在此過程中可以調整GC觸發時間, 模擬真實的故障場景 ?
    5. 看看GC後堆的大小是否增長, 如果沒有不斷增長, 並且持續一段較長時間, 那基本正常(具體看看深入淺出JProfiler文章中的實踐章節)。

    注: Java記憶體洩露的場景還有很多(可以參考下官方的一些文件java memleaks), 有機會後面會繼續補充.


相關文章