Java開發中出現OOM的常見原因有哪些?

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

1、Java heap space

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

原因分析

Javaheap space錯誤產生的常見原因可以分為以下幾類:

1、請求創建一個超大對象,通常是一個大數組。

2、超出預期的訪問量/數據量,通常是上游系統請求流量飆升,常見于各類促銷/秒殺活動,可以結合業務流量指標排查是否有尖狀峰值。

3、過度使用終結器(Finalizer),該對象沒有立即被GC。

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

2、GC overhead limit exceeded

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

3、Permgen space

該錯誤表示永久代(Permanent Generation)已用滿,通常是因為加載的class數目太多或體積太大。

原因分析

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

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

2、常量池;

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

4、JIT編譯器優化后的class信息。

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

4、Metaspace

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

需要特別注意的是調整Metaspace空間大小的啟動參數為-XX:MaxMetaspaceSize。

5、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錯誤。

6、Out of swap space?

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

原因分析

該錯誤出現的常見原因包括以下幾類:

1、地址空間不足;

2、物理內存已耗光;

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

4、執行jmap-histo:live命令,強制執行Full GC;如果幾次執行后內存明顯下降,則基本確認為Direct ByteBuffer問題。

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

8、Requested array size exceeds VM limit

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

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

此類問題比較罕見,通常需要檢查代碼,確認業務是否需要創建如此大的數組,是否可以拆分為多個塊,分批執行。

9、Direct buffer memory

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

原因分析

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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 什么是OOM?OOM時Java開發中不可避免的一個常見問題,尤其在面試時,面試官會經常問到“談談你對OOM的認識”...
    88b0c6858840閱讀 294評論 0 0
  • 一 Java heap space 當堆內存(Heap Space)沒有足夠空間存放新創建的對象時,就會拋出 ja...
    花神子閱讀 2,891評論 0 5
  • 轉自Java內存溢出(OOM)異常完全指南 我的職業生涯中見過數以千計的內存溢出異常均與下文中的8種情況相關。本文...
    SunnyMore閱讀 1,999評論 0 17
  • 設置,如果你沒有顯式設置,則將使用特定平臺的默認值。 當應用程序試圖向堆空間添加更多的數據,但堆卻沒有足夠的空間來...
    java高并發閱讀 884評論 1 2
  • 16宿命:用概率思維提高你的勝算 以前的我是風險厭惡者,不喜歡去冒險,但是人生放棄了冒險,也就放棄了無數的可能。 ...
    yichen大刀閱讀 6,099評論 0 4