當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錯誤。