乾貨 | 值得收藏:解讀分析9種 OOM 常見緣由及解決方案

做者:涯海html

原文地址:github.com/StabilityMa…java

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

一、Java heap space

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

緣由分析數據庫

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

  1. 請求建立一個超大對象,一般是一個大數組。緩存

  2. 超出預期的訪問量/數據量,一般是上游系統請求流量飆升,常見於各種促銷/秒殺活動,能夠結合業務流量指標排查是否有尖狀峯值。服務器

  3. 過分使用終結器(Finalizer),該對象沒有當即被 GC。markdown

  4. 內存泄漏(Memory Leak),大量對象引用沒有釋放,JVM 沒法對其自動回收,常見於使用了 File 等資源沒有回收。數據結構

解決方案

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

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

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

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

二、GC overhead limit exceeded

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

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

三、Permgen space

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

緣由分析

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

  1. 加載/緩存到內存中的 class 定義,包括類的名稱,字段,方法和字節碼;

  2. 常量池;

  3. 對象數組/類型數組所關聯的 class;

  4. JIT 編譯器優化後的 class 信息。

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

解決方案

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

  1. 程序啓動報錯,修改 -XX:MaxPermSize 啓動參數,調大永久代空間。

  2. 應用從新部署時報錯,極可能是沒有應用沒有重啓,致使加載了多份 class 信息,只需重啓 JVM 便可解決。

  3. 運行時報錯,應用程序可能會動態建立大量 class,而這些 class 的生命週期很短暫,可是 JVM 默認不會卸載 class,能夠設置 -XX:+CMSClassUnloadingEnabled-XX:+UseConcMarkSweepGC這兩個參數容許 JVM 卸載 class。

若是上述方法沒法解決,能夠經過 jmap 命令 dump 內存對象 jmap-dump:format=b,file=dump.hprof<process-id> ,而後利用 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 內存分配;

五、JVM 將拋出 java.lang.OutOfMemoryError:Unableto createnewnativethread錯誤。

解決方案

  1. 升級配置,爲機器提供更多的內存;

  2. 下降 Java Heap Space 大小;

  3. 修復應用程序的線程泄漏問題;

  4. 限制線程池大小;

  5. 使用 -Xss 參數減小線程棧的大小;

  6. 調高 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? 錯誤。

緣由分析

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

  1. 地址空間不足;

  2. 物理內存已耗光;

  3. 應用程序的本地內存泄漏(native leak),例如不斷申請本地內存,卻不釋放。

  4. 執行 jmap-histo:live<pid> 命令,強制執行 Full GC;若是幾回執行後內存明顯降低,則基本確認爲 Direct ByteBuffer 問題。

解決方案

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

  1. 升級地址空間爲 64 bit;

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

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

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

七、 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,尋找評分低的進程,並將其「殺死」,釋放內存資源。

解決方案

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

  2. 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.Cleanerclean() 方法來主動釋放被 Direct ByteBuffer 持有的內存空間。

  6. 內存容量確實不足,升級配置。

推薦工具&產品

JVM 內存分析工具 mat

一、Eclipse Memory Analyzer

www.eclipse.org/mat

(a)(li)(雲) APM 產品,支持 OOM 異常關鍵字告警

二、ARMS

help.aliyun.com/document_de…

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

三、alibaba Arthas

github.com/alibaba/art…

參考文章

一、Plumbr OutOfMemoryError(推薦,含代碼示例)

二、GCeasy OutOfMemoryError

三、JVM 內存結構

共同進步,學習分享

歡迎你們關注個人公衆號【風平浪靜如碼】,海量Java相關文章,學習資料都會在裏面更新,整理的資料也會放在裏面。

以爲寫的還不錯的就點個贊,加個關注唄!點關注,不迷路,持續更新!!!

相關文章
相關標籤/搜索