簡介
- JNI方法申請的native 內存,通常是在JDK庫裏;本地 C++ 方法直接通過 malloc申請的內存,不受JVM管控。
- 堆內內存: 指Java堆,GC算法管理的內存區域。
- 堆外內存: Java堆外的內存都叫堆外。可以細分爲JVM內部,Metaspace, JNI方法申請的native內存三部分。通常說的直接內存 DirectBuffer , 會記錄在Internal區。
JNI申請的native內存不受JVM管控。
GC算法和日誌解讀
- Java堆進一步劃分,Eden, s0, s1, 老年代。
- 不同GC算法對存活對象的掃描和清理方式不一樣,對業務代碼的運行影響不一樣,因此,不同的業務場景,選合適的GC算法和GC參數。
一般建議 parallel scavenge (JDK8默認GC),適用大部分場景。
GC日誌組成: GC時間, GC原因, GC位置(年輕代, 老年代, 元空間), 釋放內存大小, 持續時間。
原則上, 優先看Full GC的頻率, 其次查看日誌STW階段(比如G1裏標記了pause, CMS裏mark和remark)階段消耗時間,
最後查看內存收縮情況。
用JMap工具可以轉儲Java堆到快照文件,然後用MAT工具分析,只要是堆內問題,就用MAT工具。
- 可以詳細查看Java堆內java對象佔比
- 可多維度分析堆內對象分佈,查找可疑的內存最大佔比的對象。
- 可分析對象的依賴路徑,查找爲是否原因。
GC異常分析與定位
現象:
- 業務偶爾出現超時現象
- cpu負載很高
分析與解決:
- 超時或負載高很可能是GC異常表象,此時需要查看GC日誌,結合實際業務,分析GC行爲,找到GC異常根因。
- GC頻繁,STW時間長,可能原因有:存活對象多,堆大小或堆各子區域大小劃分不合理。
- 存活對象多,則可能存在內存泄漏,可能是代碼邏輯問題,進一步可用MAT工具分析Java對象。
OOM問題分析與定位
1. 根據OOM錯誤提示,由於配置原因導致Java堆或者Metaspace區域oom,
判斷標準之一是程序是否穩定運行,實際內存佔用有升有降。
2. Metaspace本身沒有限制,如果設置了MaxMetaspaceSize,則受限這個配置。
3.如果內存持續增長超出業務實際可能使用的內存量,則可能存在內存泄漏。
內存泄漏位置判斷
1. 通過異常日誌提示,
eg, Java.lang.OutOfMemoryError: Java heap space,
Java.lang.OutOfMemoryError: metadata space,
Java.lang.OutOfMemoryError: Direct buffer space,
2. 開啓NMT(啓動參數上加上 -XX: NativeMemoryTracking=detail), 可幫助判斷那塊內存區域內存泄漏。
- Java堆,即Java Heap
- Metaspace, 即class區域
- JVM內部
- JNI申請的native內存
堆內內存泄漏排查
1. 只要是Java堆或Metaspace區域內存泄漏,都可以轉儲Java堆快照文件,用MAT工具分析。
2. 多維度分析堆內對象分佈,查找可疑內存最大佔比的對象。
3.分析對象依賴路徑,查找未釋放原因。
堆外內存泄漏排查
1. 首先通過上文手段判斷內存泄漏位置。
2.根據位置對應的功能,進一步查找具體的異常代碼。
3. JNI申請的native內存本身已超出JVM控制範圍,沒辦法用JVM工具排查。
藉助操作系統工具pmap排查,可查看進程內存映射,查找可疑內存。
案例分析
例一.
某業務發現cpu值偶爾飆升,排查發現GC停頓較長。
進一步發現大量存活對象有些異常,懷疑內存泄漏。
最後heap dump 後使用MAT分析,發現一全局HashMap持有了大量重複對象,
業務代碼裏移除操作由問題,實際未移除,修復後問題解決。
例二.
某業務線遷移OSGI框架後,JVM無法響應外部請求,排查GC日誌發現是因爲Metaspace空間不足,
頻繁full gc 導致;進一步排查發現OSGI環境下,不停的defineClass, 最後發現某基礎組件會不停創建,
實際上, 全局只需一份,修改後問題解決。
例三.
某業務線集羣,進程RSS超出Xmx很多,開啓NMT後發現確實有不受JVM控制的內存申請,
最後通過監控 malloc 監控每次請求,發現一處本地方法調用,sun.nio.fs.unitNativeDispatcher.opendir,
進一步分析,代碼中有使用Files.list函數,但沒有釋放動作。