首頁  >  文章  >  Java  >  面試官:如果線上遇到了OOM,該如何解決?

面試官:如果線上遇到了OOM,該如何解決?

Java后端技术全栈
Java后端技术全栈轉載
2023-08-17 16:38:451067瀏覽

OOM 可以說是我們開發者最害怕的問題之一,導致的原因基本上都是由程式碼或 JVM 參數配置引起的。

這篇文章跟讀者聊聊,Java 進程觸發了 OOM 後如何排查。

常說對生產環境保持敬畏之心,快速解決問題也是一種敬畏的表現

面試官:如果線上遇到了OOM,該如何解決?

為什麼會OOM

OOM 全名為“Out Of Memory”,表示記憶體耗盡。當JVM 因為沒有足夠的記憶體來為物件分配空間,而垃圾回收器也已經沒有空間可回收時,就會拋出這個錯誤

為什麼會出現OOM,一般由這些問題引起

  1. 分配過少:JVM 初始化記憶體小,業務使用了大量記憶體;或不同JVM 區域分配記憶體不合理
  2. 程式碼漏洞:某一個對象經常被申請,不用了之後卻沒有被釋放,導致記憶體耗盡

記憶體洩漏:申請使用完的記憶體沒有釋放,導致虛擬機器不能再使用該內存,此時這段內存就洩漏了。因為申請者不用了,但又不能被虛擬機器分配給別人用

記憶體溢出:申請的記憶體超出了JVM 所能提供的記憶體大小,此時稱之為溢位

記憶體洩漏持續存在,最後一定會溢出,兩者是因果關係

#常見的OOM

#比較常見的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 排查想法

#查看JVM 記憶體分佈

假設我們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 記憶體物件清單依照物件所佔記憶體大小排序

  • instances:實例數
  • bytes:單位byte
  • class name:類別名稱
面試官:如果線上遇到了OOM,該如何解決?

明顯看到CustomObjTest 物件實例以及佔用記憶體過多

可惜的是,方案有其限制,因為它只能排查物件佔用記憶體過高問題

其中"[" 代表數組,例如"[C" 代表Char 數組,"[B" 代表Byte 數組。如果陣列記憶體佔用過多,我們不知道哪些物件持有它,所以就需要Dump 記憶體進行離線分析

jmap -histo:live 執行此命令,JVM 會先觸發GC,再統計資料

Dump 檔案分析

Dump 檔案是Java 進程的內存鏡像,其中主要包括系統資訊虛擬機屬性完整的執行緒Dump所有類別和物件的狀態 等資訊

當程式發生記憶體溢出或GC 異常情況時,懷疑JVM 發生了記憶體洩漏,這時我們可以匯出Dump 檔案分析

JVM 啟動參數配置新增以下參數

  • -XX:+HeapDumpOnOutOfMemoryError
  • -XX:HeapDumpPath=./(参数为 Dump 文件生成路径)

当 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 二进制文件在当前文件夹下

面試官:如果線上遇到了OOM,該如何解決?

JvisualVM 分析

Dump 分析工具有很多,相对而言 JvisualVMJProfilerEclipse Mat,使用人群更多一些。下面以 JvisualVM 举例分析 Dump 文件

面試官:如果線上遇到了OOM,該如何解決?

列举两个常用的功能,第一个是能看到触发 OOM 的线程堆栈,清晰得知程序溢出的原因

面試官:如果線上遇到了OOM,該如何解決?

第二个就是可以查看 JVM 内存里保留大小最大的对象,可以自由选择排查个数

面試官:如果線上遇到了OOM,該如何解決?

点击对象还可以跳转具体的对象引用详情页面

面試官:如果線上遇到了OOM,該如何解決?

文中 Dump 文件较为简单,而正式环境出错的原因五花八门,所以不对该 Dump 文件做深度解析

注意:JvisualVM 如果分析大 Dump 文件,可能会因为内存不足打不开,需要调整默认的内存

總結回顧

線上如遇到JVM 記憶體溢出,可以分以下幾步排查

  1. jmap -heap 查看是否記憶體分配過小
  2. #jmap -histo 查看是否有明顯的物件分配過多且沒有釋放情況
  3. jmap -dump 匯出JVM 目前記憶體快照,使用JDK 自帶或MAT 等工具分析快照

如果上面還不能定位問題,那麼需要排查應用程式是否不斷創建資源,例如網路連線或線程,都可能會導致系統資源耗盡。

#

以上是面試官:如果線上遇到了OOM,該如何解決?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:Java后端技术全栈。如有侵權,請聯絡admin@php.cn刪除