OOM 可以說是我們開發者最害怕的問題之一,導致的原因基本上都是由程式碼或 JVM 參數配置引起的。
這篇文章跟讀者聊聊,Java 進程觸發了 OOM 後如何排查。
常說對生產環境保持敬畏之心,快速解決問題也是一種敬畏的表現
OOM 全名為“Out Of Memory”,表示記憶體耗盡。當JVM 因為沒有足夠的記憶體來為物件分配空間,而垃圾回收器也已經沒有空間可回收時,就會拋出這個錯誤
為什麼會出現OOM,一般由這些問題引起
記憶體洩漏:申請使用完的記憶體沒有釋放,導致虛擬機器不能再使用該內存,此時這段內存就洩漏了。因為申請者不用了,但又不能被虛擬機器分配給別人用
記憶體溢出:申請的記憶體超出了JVM 所能提供的記憶體大小,此時稱之為溢位
記憶體洩漏持續存在,最後一定會溢出,兩者是因果關係
#比較常見的OOM 類型有以下幾種
java.lang.OutOfMemoryError: PermGen space
Java7 永久代(方法區)溢出,它用於儲存已被虛擬機器載入的類別資訊、常數、靜態變數、即時編譯器編譯後的程式碼等資料。每當一個類別初次載入的時候,元資料就會存放到永久代
一般出現於大量Class 物件或JSP 頁面,或採用CgLib 動態代理技術導致
我們可以透過-XX:PermSize
和-XX:MaxPermSize
修改方法區大小
Java8 將永久代變更為元空間,並報錯:java.lang.OutOfMemoryError: Metadata space,元空間記憶體不足預設進行動態擴充
#java.lang.StackOverflowError
虛擬機堆疊溢位,一般是由於程式中存在死迴圈或深度遞歸呼叫 造成的。如果堆疊大小設定過小也會出現溢出,可以透過-Xss
設定堆疊的大小
虛擬機器拋出堆疊溢出錯誤,可以在日誌中定位到錯誤的類別、方法
java.lang.OutOfMemoryError: Java heap space
Java 堆記憶體溢出,溢出的原因一般由於JVM 堆記憶體設定不合理或者記憶體洩漏導致
如果是記憶體洩漏,可以透過工具查看洩漏物件到GC Roots 的參考鏈。掌握了洩漏對象的類型資訊以及GC Roots 引用鏈信息,就可以精準地定位出洩漏代碼的位置
如果不存在內存洩漏,就是內存中的對象確實都還必須存活著,那就應該檢查虛擬機器的堆疊參數(-Xmx 與-Xms),查看是否可以將虛擬機器的記憶體調大些
小結:方法區和虛擬機器棧的溢位場景不在本篇過多討論,下面主要講解常見的Java 堆空間的OOM 排查想法
假設我們Java 應用PID 為15162,輸入指令查看JVM 記憶體分佈jmap -heap 15162
[xxx@xxx ~]# jmap -heap 15162 Attaching to process ID 15162, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.161-b12 using thread-local object allocation. Mark Sweep Compact GC Heap Configuration: MinHeapFreeRatio = 40 # 最小堆使用比例 MaxHeapFreeRatio = 70 # 最大堆可用比例 MaxHeapSize = 482344960 (460.0MB) # 最大堆空间大小 NewSize = 10485760 (10.0MB) # 新生代分配大小 MaxNewSize = 160759808 (153.3125MB) # 最大新生代可分配大小 OldSize = 20971520 (20.0MB) # 老年代大小 NewRatio = 2 # 新生代比例 SurvivorRatio = 8 # 新生代与 Survivor 比例 MetaspaceSize = 21807104 (20.796875MB) # 元空间大小 CompressedClassSpaceSize = 1073741824 (1024.0MB) # Compressed Class Space 空间大小限制 MaxMetaspaceSize = 17592186044415 MB # 最大元空间大小 G1HeapRegionSize = 0 (0.0MB) # G1 单个 Region 大小 Heap Usage: # 堆使用情况 New Generation (Eden + 1 Survivor Space): # 新生代 capacity = 9502720 (9.0625MB) # 新生代总容量 used = 4995320 (4.763908386230469MB) # 新生代已使用 free = 4507400 (4.298591613769531MB) # 新生代剩余容量 52.56726495150862% used # 新生代使用占比 Eden Space: capacity = 8454144 (8.0625MB) # Eden 区总容量 used = 4029752 (3.8430709838867188MB) # Eden 区已使用 free = 4424392 (4.219429016113281MB) # Eden 区剩余容量 47.665996699370154% used # Eden 区使用占比 From Space: # 其中一个 Survivor 区的内存分布 capacity = 1048576 (1.0MB) used = 965568 (0.92083740234375MB) free = 83008 (0.07916259765625MB) 92.083740234375% used To Space: # 另一个 Survivor 区的内存分布 capacity = 1048576 (1.0MB) used = 0 (0.0MB) free = 1048576 (1.0MB) 0.0% used tenured generation: # 老年代 capacity = 20971520 (20.0MB) used = 10611384 (10.119804382324219MB) free = 10360136 (9.880195617675781MB) 50.599021911621094% used 10730 interned Strings occupying 906232 bytes.
透過查看JVM 記憶體分配以及執行時使用情況,可以判斷記憶體分配是否合理
#另外,可以在JVM 運行時查看最耗費資源的對象,jmap -histo:live 15162 | more
JVM 記憶體物件清單依照物件所佔記憶體大小排序
明顯看到CustomObjTest
物件實例以及佔用記憶體過多
可惜的是,方案有其限制,因為它只能排查物件佔用記憶體過高問題
其中"[" 代表數組,例如"[C" 代表Char 數組,"[B" 代表Byte 數組。如果陣列記憶體佔用過多,我們不知道哪些物件持有它,所以就需要Dump 記憶體進行離線分析
jmap -histo:live
執行此命令,JVM 會先觸發GC,再統計資料
Dump 檔案是Java 進程的內存鏡像,其中主要包括系統資訊、虛擬機屬性、完整的執行緒Dump、所有類別和物件的狀態 等資訊
當程式發生記憶體溢出或GC 異常情況時,懷疑JVM 發生了記憶體洩漏,這時我們可以匯出Dump 檔案分析
JVM 啟動參數配置新增以下參數
当 JVM 发生 OOM 异常自动导出 Dump 文件,文件名称默认格式:
java_pid{pid}.hprof
上面配置是在应用抛出 OOM 后自动导出 Dump,或者可以在 JVM 运行时导出 Dump 文件
jmap -dump:file=[文件路径] [pid] # 示例 jmap -dump:file=./jvmdump.hprof 15162
在本地写一个测试代码,验证下 OOM 以及分析 Dump 文件
设置 VM 参数:-Xms3m -Xmx3m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./ public static void main(String[] args) { List<Object> oomList = Lists.newArrayList(); // 无限循环创建对象 while (true) { oomList.add(new Object()); } }
通过报错信息得知,java heap space
表示 OOM 发生在堆区,并生成了 hprof 二进制文件在当前文件夹下
JvisualVM 分析
Dump 分析工具有很多,相对而言 JvisualVM、JProfiler、Eclipse Mat,使用人群更多一些。下面以 JvisualVM 举例分析 Dump 文件
列举两个常用的功能,第一个是能看到触发 OOM 的线程堆栈,清晰得知程序溢出的原因
第二个就是可以查看 JVM 内存里保留大小最大的对象,可以自由选择排查个数
点击对象还可以跳转具体的对象引用详情页面
文中 Dump 文件较为简单,而正式环境出错的原因五花八门,所以不对该 Dump 文件做深度解析
注意:JvisualVM 如果分析大 Dump 文件,可能会因为内存不足打不开,需要调整默认的内存
線上如遇到JVM 記憶體溢出,可以分以下幾步排查
jmap -heap
查看是否記憶體分配過小#jmap -histo
查看是否有明顯的物件分配過多且沒有釋放情況jmap -dump
匯出JVM 目前記憶體快照,使用JDK 自帶或MAT 等工具分析快照如果上面還不能定位問題,那麼需要排查應用程式是否不斷創建資源,例如網路連線或線程,都可能會導致系統資源耗盡。
以上是面試官:如果線上遇到了OOM,該如何解決?的詳細內容。更多資訊請關注PHP中文網其他相關文章!