推薦收藏 | 9種 OOM 常見緣由及解決方案!

導讀:當 JVM 內存嚴重不足時,就會拋出 java.lang.OutOfMemoryError 錯誤。本文總結了常見的 OOM 緣由及其解決方法,以下圖所示。若有遺漏或錯誤,歡迎補充指正。html

一、Java heap space

當堆內存(Heap Space)沒有足夠空間存放新建立的對象時,就會拋出 java.lang.OutOfMemoryError:Javaheap space錯誤(根據實際生產經驗,能夠對程序日誌中的 OutOfMemoryError 配置關鍵字告警,一經發現,當即處理)。java

- 緣由分析

Javaheap space錯誤產生的常見緣由能夠分爲如下幾類:git

  • 請求建立一個超大對象,一般是一個大數組。
  • 超出預期的訪問量/數據量,一般是上游系統請求流量飆升,常見於各種促銷/秒殺活動,能夠結合業務流量指標排查是否有尖狀峯值。
  • 過分使用終結器(Finalizer),該對象沒有當即被 GC。
  • 內存泄漏(Memory Leak),大量對象引用沒有釋放,JVM 沒法對其自動回收,常見於使用了 File 等資源沒有回收。

- 解決方案

針對大部分狀況,一般只須要經過 -Xmx參數調高 JVM 堆內存空間便可。若是仍然沒有解決,能夠參考如下狀況作進一步處理:github

  • 若是是超大對象,能夠檢查其合理性,好比是否一次性查詢了數據庫所有結果,而沒有作結果數限制。數據庫

  • 若是是業務峯值壓力,能夠考慮添加機器資源,或者作限流降級。數組

  • 若是是內存泄漏,須要找到持有的對象,修改代碼設計,好比關閉沒有釋放的鏈接。緩存

二、GC overhead limit exceeded

當 Java 進程花費 98% 以上的時間執行 GC,但只恢復了不到 2% 的內存,且該動做連續重複了 5 次,就會拋出 java.lang.OutOfMemoryError:GC overhead limit exceeded錯誤。簡單地說,就是應用程序已經基本耗盡了全部可用內存, GC 也沒法回收。服務器

此類問題的緣由與解決方案跟 Javaheap space很是相似,能夠參考上文。markdown

三、Permgen space

該錯誤表示永久代(Permanent Generation)已用滿,一般是由於加載的 class 數目太多或體積太大。數據結構

- 緣由分析

永久代存儲對象主要包括如下幾類:

  1. 加載/緩存到內存中的 class 定義,包括類的名稱,字段,方法和字節碼;
  2. 常量池;
  3. 對象數組/類型數組所關聯的 class;
  4. JIT 編譯器優化後的 class 信息。

PermGen 的使用量與加載到內存的 class 的數量/大小正相關。

- 解決方案

根據 Permgen space 報錯的時機,能夠採用不一樣的解決方案,以下所示:

  • 程序啓動報錯,修改 -XX:MaxPermSize啓動參數,調大永久代空間。
  • 應用從新部署時報錯,極可能是沒有應用沒有重啓,致使加載了多份 class 信息,只需重啓 JVM 便可解決。
  • 運行時報錯,應用程序可能會動態建立大量 class,而這些 class 的生命週期很短暫,可是 JVM 默認不會卸載 class,能夠設置 -XX:+CMSClassUnloadingEnabled和 -XX:+UseConcMarkSweepGC這兩個參數容許 JVM 卸載 class。

若是上述方法沒法解決,能夠經過 jmap 命令 dump 內存對象 jmap-dump:format=b,file=dump.hprof,而後利用 Eclipse MAT www.eclipse.org/mat功能逐一分析開銷最大的 classloader 和重複 class。

四、Metaspace

JDK 1.8 使用 Metaspace 替換了永久代(Permanent Generation),該錯誤表示 Metaspace 已被用滿,一般是由於加載的 class 數目太多或體積太大。

此類問題的緣由與解決方法跟 Permgenspace很是相似,能夠參考上文。須要特別注意的是調整 Metaspace 空間大小的啓動參數爲 -XX:MaxMetaspaceSize。

五、Unable to create new native thread

每一個 Java 線程都須要佔用必定的內存空間,當 JVM 向底層操做系統請求建立一個新的 native 線程時,若是沒有足夠的資源分配就會報此類錯誤。

- 緣由分析

JVM 向 OS 請求建立 native 線程失敗,就會拋出 Unableto createnewnativethread,常見的緣由包括如下幾類:

  1. 線程數超過操做系統最大線程數 ulimit 限制;
  2. 線程數超過 kernel.pid_max(只能重啓);
  3. native 內存不足;

該問題發生的常見過程主要包括如下幾步:

  1. JVM 內部的應用程序請求建立一個新的 Java 線程;
  2. JVM native 方法代理了該次請求,並向操做系統請求建立一個 native 線程;
  3. 操做系統嘗試建立一個新的 native 線程,併爲其分配內存;
  4. 若是操做系統的虛擬內存已耗盡,或是受到 32 位進程的地址空間限制,操做系統就會拒絕本次 native 內存分配;
  5. JVM 將拋出 java.lang.OutOfMemoryError:Unableto createnewnativethread錯誤。

- 解決方案

  • 升級配置,爲機器提供更多的內存;
  • 下降 Java Heap Space 大小;
  • 修復應用程序的線程泄漏問題;
  • 限制線程池大小;
  • 使用 -Xss 參數減小線程棧的大小;
  • 調高 OS 層面的線程最大數:執行 ulimia-a查看最大線程數限制,使用 ulimit-u xxx調整最大線程數限制。

ulimit -a .... 省略部份內容 ..... max user processes (-u) 16384

六、Out of swap space?

該錯誤表示全部可用的虛擬內存已被耗盡。虛擬內存(Virtual Memory)由物理內存(Physical Memory)和交換空間(Swap Space)兩部分組成。當運行時程序請求的虛擬內存溢出時就會報 Outof swap space?錯誤。

- 緣由分析

該錯誤出現的常見緣由包括如下幾類:

  • 地址空間不足;
  • 物理內存已耗光;
  • 應用程序的本地內存泄漏(native leak),例如不斷申請本地內存,卻不釋放。
  • 執行 jmap-histo:live命令,強制執行 Full GC;若是幾回執行後內存明顯降低,則基本確認爲 Direct ByteBuffer 問題。

- 解決方案

根據錯誤緣由能夠採起以下解決方案:

  • 升級地址空間爲 64 bit;

  • 使用 Arthas 檢查是否爲 Inflater/Deflater 解壓縮問題,若是是,則顯式調用 end 方法。

  • Direct ByteBuffer 問題能夠經過啓動參數 -XX:MaxDirectMemorySize調低閾值。

  • 升級服務器配置/隔離部署,避免爭用。

七、 Kill process or sacrifice child

有一種內核做業(Kernel Job)名爲 Out of Memory Killer,它會在可用內存極低的狀況下「殺死」(kill)某些進程。OOM Killer 會對全部進程進行打分,而後將評分較低的進程「殺死」,具體的評分規則能夠參考 Surviving the Linux OOM Killer。

不一樣於其餘的 OOM 錯誤, Killprocessorsacrifice child錯誤不是由 JVM 層面觸發的,而是由操做系統層面觸發的。

- 緣由分析

默認狀況下,Linux 內核容許進程申請的內存總量大於系統可用內存,經過這種「錯峯複用」的方式能夠更有效的利用系統資源。

然而,這種方式也會無可避免地帶來必定的「超賣」風險。例如某些進程持續佔用系統內存,而後致使其餘進程沒有可用內存。此時,系統將自動激活 OOM Killer,尋找評分低的進程,並將其「殺死」,釋放內存資源。

- 解決方案

  • 升級服務器配置/隔離部署,避免爭用。

  • OOM Killer 調優。

八、Requested array size exceeds VM limit

JVM 限制了數組的最大長度,該錯誤表示程序請求建立的數組超過最大長度限制。

JVM 在爲數組分配內存前,會檢查要分配的數據結構在系統中是否可尋址,一般爲 Integer.MAX_VALUE-2。

此類問題比較罕見,一般須要檢查代碼,確認業務是否須要建立如此大的數組,是否能夠拆分爲多個塊,分批執行。

九、Direct buffer memory

Java 容許應用程序經過 Direct ByteBuffer 直接訪問堆外內存,許多高性能程序經過 Direct ByteBuffer 結合內存映射文件(Memory Mapped File)實現高速 IO。

- 緣由分析

Direct ByteBuffer 的默認大小爲 64 MB,一旦使用超出限制,就會拋出 Directbuffer memory錯誤。

- 解決方案

  1. Java 只能經過 ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer,所以,能夠經過 Arthas 等在線診斷工具攔截該方法進行排查。
  2. 檢查是否直接或間接使用了 NIO,如 netty,jetty 等。
  3. 經過啓動參數 -XX:MaxDirectMemorySize調整 Direct ByteBuffer 的上限值。
  4. 檢查 JVM 參數是否有 -XX:+DisableExplicitGC選項,若是有就去掉,由於該參數會使 System.gc()失效。
  5. 檢查堆外內存使用代碼,確認是否存在內存泄漏;或者經過反射調用 sun.misc.Cleaner的 clean()方法來主動釋放被 Direct ByteBuffer 持有的內存空間。
  6. 內存容量確實不足,升級配置。

推薦工具&產品

JVM 內存分析工具 mat

  • Eclipse Memory Analyzer

www.eclipse.org/mat

阿里雲 APM 產品,支持 OOM 異常關鍵字告警

  • ARMS

help.aliyun.com/document_de…

阿里 Java 在線診斷工具 Arthas(阿爾薩斯)

  • alibaba Arthas

github.com/alibaba/art…

做者 | 智哥

原文連接

更多技術乾貨敬請關注碼農架構技術號:碼農架構 - 掘金

本文爲**碼農架構**原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索