1. 程式人生 > >排查Java 記憶體洩露-藉助排查工具

排查Java 記憶體洩露-藉助排查工具

轉自:https://juejin.im/entry/57fb07255bbb50005b2f20ac

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

      • **用JProfiler來trace String.intern方法棧
    • 方案2: dump heap, 看看哪些class有異常現象(數量), String被Perm區引用的物件資訊等.但這種方式不太直觀,可以從String資料看看發現可疑問題,沒有方案1直觀。(如下圖: 如果能在日常除錯推薦JProfiler**)

    _2016_09_29_1_46_59

    • 方案3: 增加-XX:+TraceClassLoading和-XX:+TraceClassUnloading, 看看哪些class載入了,哪些class解除安裝了. 如果一些特殊的class一直被載入而沒有被解除安裝說明也是有問題的。(如下圖)
 _2016_09_29_1_06_46
    • 方案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][JProfiler]文章中的實踐章節)。