【問題排查系列】JDK1.8 下記憶體不斷增長排查及解決

羊都是我吃的發表於2022-01-12

概述

通過對搜尋叢集中的記憶體不斷增長問題的排查,總結排查記憶體方面的方法和經驗。以便記錄和參考。

問題表現

  • 釋出之後機器記憶體不斷上漲。需要重啟才能得到解決。

解決過程

jmap 排查堆記憶體階段

  • 使用傳統的方式dump記憶體,然後使用Jprofiler或mat進行分析。
  • 這裡使用了對照的方式,即在重啟後立即dump堆和執行一天後dump進行快照比對,發現差異較小,且使用了dump:live 和非live兩種方式,均未發現堆內有明顯異常。
  • 詳細操作方式這裡不在贅述。

    使用NMT排查堆外記憶體(native memory)

  • 需要新增引數 -XX:NativeMemoryTracking=detail 據說新增了之後效能會下降 5% ~ 10% ,我新增了,沒下降。不過還是建議線上慎重,新增一臺機器排查問題即可。
  • 設定NMT 的基線: jcmd <pid> VM.native_memory baseline 設定基線之後即可標記一個基準記憶體狀態,過一段時間之後可以比較記憶體的變化,哪部分增長的最多。
  • 過一段時間後,使用 jcmd <pid> VM.native_memory detail.diff scale=MB 檢視記憶體的變化。
  • 經過一段時間之後變化如下:

Native Memory Tracking:

Total: reserved=15048MB +73MB, committed=13993MB +74MB

-                 Java Heap (reserved=10240MB, committed=10240MB)
                            (mmap: reserved=10240MB, committed=10240MB)

-                     Class (reserved=1224MB, committed=223MB)
                            (classes #30779 +1)
                            (malloc=6MB #100242 +153)
                            (mmap: reserved=1218MB, committed=218MB)

-                    Thread (reserved=1457MB +5MB, committed=1457MB +5MB)
                            (thread #1444 +4)
                            (stack: reserved=1449MB +5MB, committed=1449MB +5MB)
                            (malloc=5MB #7227 +20)
                            (arena=3MB #2887 +8)

-                      Code (reserved=286MB, committed=251MB)
                            (malloc=42MB #41476 +94)
                            (mmap: reserved=244MB, committed=209MB)

-                        GC (reserved=520MB +16MB, committed=520MB +16MB)
                            (malloc=108MB +16MB #153709 +200)
                            (mmap: reserved=412MB, committed=412MB)

-                  Compiler (reserved=5MB, committed=5MB)
                            (malloc=5MB #5673 +3)

-                  Internal (reserved=610MB +3MB, committed=610MB +3MB)
                            (malloc=609MB +3MB #168363 +191)

-                    Symbol (reserved=672MB +50MB, committed=672MB +50MB)
                            (malloc=667MB +50MB #465680 +6396)
                            (arena=5MB #1)

-    Native Memory Tracking (reserved=15MB, committed=15MB)
                            (malloc=1MB #9771 +3390)
                            (tracking overhead=15MB)

-                   Unknown (reserved=20MB, committed=0MB)
                            (mmap: reserved=20MB, committed=0MB)

可以看到其中的 Symbol 部分上漲明顯,這部分主要是儲存String intern等資訊。所以可以初步判斷是這部分洩露了。

尋找解決方案

  • 這裡有個基礎知識,即jdk8 對元空間的變化。可以自行google 檢視變化。jdk8 之後永久代移除,元空間存放在native memory中。
  • 在搜NMT 記憶體洩露等關鍵字時發現,jdk似乎存在bug,會造成本地記憶體洩露:https://bugs.openjdk.java.net...
  • 可以看出表現基本一致:

image
image

  • 可以看到jdk1.8 131版本發現了這個問題,而我們使用的是101版本,所以懷疑也存在這個問題,於是嘗試修改jdk版本來解決此問題。
  • 解決方案是升級jdk,於是升級到jdk1.8.0_202。

結果驗證

  • 跟上一步驟一樣,這次採用了對比的方式,即一臺機器使用原始的jdk版本(101版本),另外一臺使用202版本。經過2天的執行後,可以看到以下差距:

  • 明顯看出進行在申請記憶體方面的差異,而差異主要來源於Symbol。和jdk中的bug表現基本一致。
  • 也可以設定 -XX:MaxMetaspaceSize 對元空間進行限制,不過沒有測試。因為目前記憶體洩露的主要原因還是bug,而不是過多的產生了大量的後設資料或String interned。

整體排查思路

  • 使用jmap 檢視記憶體情況,檢視記憶體分配。
  • dump 堆快照分析堆內情況,排查記憶體洩露,注意dump會FGC,線上需下線進行。並且後來思考,如果是堆記憶體洩露其實不太會造成物理上的記憶體持續增長。因為堆的大小是確定的。
  • 堆內記憶體確認沒有問題之後排查堆外記憶體,使用NMT進行排查,設定baseline,然後隔段時間進行比對。
  • 定位問題後查詢解決方案,嘗試解決。
  • 嘗試解決後進行控制變數比對,確認問題真的解決。
  • 調整後線上穩定執行48H,確認調整沒有帶來其他副作用。

參考資料

相關文章