排查oom
用jmap生成我們的堆空間的快照Heap Dump(堆轉儲文件),來分析我們的內存占用
用可視化工具,例如java中的jhat分析Heap Dump文件 ,它分析完會通過一個瀏覽器打開一個可視化頁面展示分析結果
根據oom的類型來調整我們的內存配置
java.lang.OutOfMemoryError: Java heap space:堆內存不足。java.lang.OutOfMemoryError: Metaspace:元空間(類元數據)不足。java.lang.OutOfMemoryError: Direct buffer memory:直接內存(NIO)不足。java.lang.OutOfMemoryError: Unable to create new native thread:線程數超出限制。java.lang.OutOfMemoryError: GC overhead limit exceeded:GC 頻繁且回收效率低
調整jvm參數
堆內存不足:
-Xms512m -Xmx4g # 初始堆和最大堆大小
-XX:+UseG1GC # 使用 G1 垃圾回收器(適合大堆)
metaspace元空間不足:
-XX:MaxMetaspaceSize=512m # 限制元空間大小
-XX:+CMSClassUnloadingEnabled # 啟用類卸載(CMS GC)
直接內存不足:
-XX:MaxDirectMemorySize=256m # 調整直接內存上限
排查宕機(JVM Crash)
1.查看崩潰日志
JVM 崩潰時會生成 hs_err_pid<pid>.log 文件,包含關鍵信息:
崩潰原因:如 SIGSEGV(非法內存訪問)、EXCEPTION_ACCESS_VIOLATION。
堆棧信息:崩潰時的線程堆棧、本地庫調用鏈
2.實時監控工具
jstat:查看 GC 統計信息
jstat -gcutil <pid> 1000 # 每秒打印 GC 情況
jstack:生成線程快照,分析死鎖或線程阻塞
jstack <pid> > thread_dump.txt
Prometheus + Grafana:監控 JVM 內存、GC、線程等指標
啟用 GC 日志
-Xloggc:/path/to/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps
JNI問題是什么?
JNI(Java Native Interface) 是 Java 提供的一種機制,允許 Java 代碼與 C/C++ 等本地(Native)代碼交互。例如,調用操作系統底層 API 或使用高性能計算庫時,可能會用到 JNI。
為什么 JNI 容易出問題?
JNI 是 Java 和本地代碼的“橋梁”,但需要手動管理內存和資源
常見問題包括:
內存泄漏:本地代碼分配的內存未釋放(如 malloc 后未 free)
越界訪問:操作數組時越界(如 GetIntArrayElements 后越界寫數據)
懸空指針:Java 對象被垃圾回收后,本地代碼仍訪問其指針
線程安全問題:本地代碼未正確處理多線程(如未綁定 JNIEnv 到線程)
兼容性問題:編譯的本地庫與操作系統或 JVM 版本不兼容(如 32/64 位不匹配)
JNI 問題導致的典型現象
JVM 崩潰:日志中出現 SIGSEGV(段錯誤)、EXCEPTION_ACCESS_VIOLATION。
內存逐漸耗盡:本地代碼內存泄漏,但 Java 堆內存正常。
應用行為異常:數據損壞、隨機崩潰(如錯誤處理指針)。
如何排查 JNI 問題?
查看崩潰日志:JVM 崩潰時生成的 hs_err_pid<pid>.log 文件,定位崩潰的本地方法和線程
本地代碼調試:
使用 GDB(Linux)或 WinDbg(Windows)調試本地庫。
用 Valgrind(Linux)檢查內存泄漏或越界訪問。
代碼審查:
確保本地代碼正確釋放資源(如 ReleaseArrayElements)。
避免跨線程使用 JNIEnv(每個線程需通過 AttachCurrentThread 獲取)。
簡化復現:隔離 JNI 調用部分,編寫最小測試用例驗證問題
GC日志
什么是GC日志?有什么用?
GC 日志(Garbage Collection Log) 是 JVM 垃圾回收過程的詳細記錄,用于分析內存管理和 GC 性能。
GC 日志的作用
診斷內存問題:
發現頻繁 Full GC(可能內存泄漏)
觀察對象晉升到老年代的速度(過早晉升導致 OOM)
優化 GC 性能:
分析 GC 暫停時間(Stop-The-World)是否影響應用響應
調整堆大小或選擇更合適的 GC 算法(如 G1、ZGC)。
驗證配置效果:
確認 JVM 參數(如 -Xmx、-XX:NewRatio)是否生效。
觀察分代內存分配是否合理
GC 日志包含哪些信息?
時間戳:GC 發生的時間。
GC 類型:GC(Minor GC)、Full GC。
觸發原因:如 Allocation Failure(分配失敗)。
內存變化:各分代(YoungGen、OldGen)回收前后的內存大小。
耗時:user(CPU 用戶態時間)、real(實際暫停時間)
GC 日志的典型分析場景
頻繁 Young GC:
現象:每秒多次 Young GC。
可能原因:新生代太小或短生命周期對象過多。
解決:增大 -Xmn(新生代大小)。
長時間 Full GC:
現象:Full GC 耗時長(如超過 1 秒)。
可能原因:老年代內存不足或內存泄漏。
解決:分析堆轉儲(Heap Dump)檢查大對象
JVM奔潰日志一般會記錄什么信息?
1. 基本信息
時間戳:記錄崩潰發生的具體時間,精確到毫秒甚至更細的粒度,有助于定位問題出現的時間點。
JVM 版本信息:包括 JVM 的供應商、版本號、構建號等,不同版本的 JVM 可能存在不同的特性和已知問題,這些信息對于分析問題很重要。
操作系統信息:如操作系統的名稱、版本、架構(32 位或 64 位)等,因為 JVM 與操作系統的交互可能會影響其穩定性,某些問題可能與特定的操作系統環境相關
2. 崩潰原因信息
錯誤類型:明確指出 JVM 崩潰的錯誤類型,如 OutOfMemoryError(內存溢出錯誤)、StackOverflowError(棧溢出錯誤)等,這些錯誤類型為問題的初步定位提供了方向。
異常信息:如果是由異常導致的崩潰,會詳細記錄異常的類型、消息以及異常發生的調用棧信息。調用棧信息能夠顯示出異常是在哪些方法中被拋出的,幫助開發人員追蹤代碼執行路徑,找到引發問題的具體代碼位置
3. 內存信息
堆內存使用情況:記錄 JVM 堆內存的大小、已使用的堆內存量、剩余的堆內存量等信息。通過這些數據可以判斷是否存在內存泄漏或內存使用不合理的情況。
非堆內存使用情況:對于 JVM 中的非堆內存,如方法區、本地方法棧等,也會有相應的使用情況記錄,有助于全面了解 JVM 的內存占用情況。
垃圾回收信息:包括垃圾回收的次數、不同代(新生代和老年代)的垃圾回收情況、垃圾回收所花費的時間等。垃圾回收如果出現異常,可能導致內存無法及時釋放,進而引發 JVM 崩潰,這些信息可以幫助分析垃圾回收是否正常工作
我們一開始什么都不知道的時候,我怎么知道這個是oom還是宕機
一、觀察現象:快速區分 OOM 和宕機
特征 | OOM(內存溢出) | JVM 宕機(Crash) |
進程是否存活 | 進程可能仍在運行(但無法處理請求) | 進程直接退出(JVM 崩潰) |
日志中的關鍵字 |
或 |
(崩潰日志) |
直接表現 | 拋出異常,可能部分功能不可用,但進程未退出 | 應用突然消失(如終端打印 ) |
是否生成堆轉儲文件 | 如果配置了 ,會生成 文件 | 不會生成堆轉儲文件,但會生成崩潰日志文件 |
二、實操步驟:快速定位問題類型
1. 第一步:檢查進程是否存在
進程還在 → 可能是 OOM 或普通錯誤(如線程阻塞)。
進程消失 → 可能是 JVM 宕機
2. 第二步:查看應用日志
快速搜索日志中的關鍵字:
# 查看最近的應用日志(替換為實際路徑)
tail -n 100 /path/to/application.log | grep -i "outofmemoryerror"
關鍵日志示例:
OOM:
java.lang.OutOfMemoryError: Java heap space
JVM 宕機:
沒有 OOM 錯誤,但進程消失。
可能在系統日志中看到 Segmentation fault(Linux)或 EXCEPTION_ACCESS_VIOLATION(Windows)
3.第三步:檢查崩潰日志(僅宕機)
如果進程消失,JVM 會生成崩潰日志 hs_err_pid<pid>.log
存在 hs_err_pid.log → JVM 宕機(需分析崩潰原因)。
不存在該日志 → 可能是 OOM 或其他問題
4.第四步:驗證 OOM 的堆轉儲文件
如果配置了 -XX:+HeapDumpOnOutOfMemoryError,OOM 時會生成 .hprof 文件
結論:
存在 .hprof 文件 → OOM。
不存在 → 可能是未配置參數,或非堆內存問題(如 Metaspace OOM)
快速總結
進程活著 + 日志有 OOM 錯誤 → OOM
進程消失 + 存在 hs_err_pid.log → JVM 宕機
進程消失 + 無崩潰日志 → 可能是系統殺死(如 OOM Killer)或其他原因
面試問答:如果我們的java程序出現了宕機或oom我們該怎么排查
我們一開始并不知道我們的程序出現錯誤是因為宕機了還是oom?
是因為服務器資源緊張還是java程序配置不合理的原因?
所以我們首先先用top命令去看看服務器整體的內存占用百分比,看看內存是否緊張,我們一般用top命令去查看,但一般都會有現成的監控工具和報警工具,例如普羅米修斯
如果服務器資源不緊張的話,那可能才是java程序本身出了問題
我們要查看java程序是否存活
進程活著 + 日志有 OOM 錯誤 → OOM
進程消失 + 存在 hs_err_pid.log → JVM 宕機
進程消失 + 無崩潰日志 → 可能是系統殺死(如 OOM Killer)或其他原因
啟動Java應用時添加 JVM 參數:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof
OOM 發生時,JVM 會自動生成堆轉儲文件到指定路徑
如果java程序仍然存活,說明出錯是因為oom,我們內存oom的話我們會生成一個headdump轉儲文件
然后我們查看我們的oom的類型
java.lang.OutOfMemoryError: Java heap space:堆內存不足。java.lang.OutOfMemoryError: Metaspace:元空間(類元數據)不足。java.lang.OutOfMemoryError: Direct buffer memory:直接內存(NIO)不足。java.lang.OutOfMemoryError: Unable to create new native thread:線程數超出限制。java.lang.OutOfMemoryError: GC overhead limit exceeded:GC 頻繁且回收效率低
Metaspace屬于非堆內存,它的OOM可能和堆OOM表現類似,即JVM進程可能仍然存在,但無法繼續加載類,導致應用無法正常運行
我們根據錯誤類型來調整我們的堆內存的空間配置
如果java程序沒有存活,說明就是宕機或者崩潰了
我們的JVM崩潰的時候會生成崩潰日志 hs_err_pid<pid>.log,里面會記錄著我們的jvm的崩潰原因信息,jvm信息,內存信息等,然后我們進行分析
這個都是出問題的時候我們根據文件排查的,但是有時候出了問題我們從文件排查不出來,我們就要用jvm工具來排查oom了
我們可以用jmap來手動生成當前堆內存的轉儲文件headdump,然后用jhat來打開我們的dump文件來分析結果
通過jconsole和普羅米修斯等可視化實時監控工具來查看堆內存,線程,類信息等
jstack分析線程,檢查線程是否阻塞在某個對象分配上(例如等待鎖導致內存無法釋放)
啟用gc日志:
-Xloggc:/path/to/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps
通過可視化gc日志GCViewer來查看gc回收相關信息