概述
通過對搜尋叢集中的記憶體不斷增長問題的排查,總結排查記憶體方面的方法和經驗。以便記錄和參考。
問題表現
- 釋出之後機器記憶體不斷上漲。需要重啟才能得到解決。
解決過程
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...
- 可以看出表現基本一致:
- 可以看到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,確認調整沒有帶來其他副作用。
參考資料
- Oracle 官方bug資訊:https://bugs.java.com/bugdata...
- JDK bug 資訊:https://bugs.openjdk.java.net...
- SymbolTable 存放的內容:https://blog.csdn.net/weixin_...
- 同樣使用NMT排查問題的另一示例:https://blog.csdn.net/qiansha...
- PermGen與MetaSpace : https://segmentfault.com/a/11...
- NMT 使用示例:https://blog.51cto.com/u_1512...