JVM 發(fā)生 OOM 的 8 種原因、及解決辦法

擼Java的同學(xué),多多少少都會(huì)碰到內(nèi)存溢出(OOM)的場(chǎng)景,但是造成OOM原因卻不止一個(gè)。

堆內(nèi)存不足

這種場(chǎng)景最為常見(jiàn),報(bào)錯(cuò)信息:

java.lang.OutOfMemoryError: Java heap space

原因

1、代碼中可能存在大對(duì)象分配
2、可能存在內(nèi)存泄露,導(dǎo)致在多次GC之后,還是無(wú)法找到一塊足夠大的內(nèi)存容納當(dāng)前對(duì)象。

解決方法

1、檢查是否存在大對(duì)象的分配,最有可能的是大數(shù)組分配
2、通過(guò)jmap命令,把堆內(nèi)存dump下來(lái),使用mat工具分析一下,檢查是否存在內(nèi)存泄露的問(wèn)題
3、如果沒(méi)有找到明顯的內(nèi)存泄露,使用 -Xmx 加大堆內(nèi)存
4、還有一點(diǎn)容易被忽略,檢查是否有大量的自定義的 Finalizable 對(duì)象,也有可能是框架內(nèi)部提供的,考慮其存在的必要性

永久代/元空間溢出

報(bào)錯(cuò)信息:

java.lang.OutOfMemoryError: PermGen space
java.lang.OutOfMemoryError: Metaspace

原因

永久代是 HotSot 虛擬機(jī)對(duì)方法區(qū)的具體實(shí)現(xiàn),存放了被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、JIT編譯后的代碼等。

JDK8后,元空間替換了永久代,元空間使用的是本地內(nèi)存,還有其它細(xì)節(jié)變化:

  • 字符串常量由永久代轉(zhuǎn)移到堆中
  • 和永久代相關(guān)的JVM參數(shù)已移除

出現(xiàn)永久代或元空間的溢出的原因可能有如下幾種:

1、在Java7之前,頻繁的錯(cuò)誤使用String.intern方法
2、生成了大量的代理類,導(dǎo)致方法區(qū)被撐爆,無(wú)法卸載
3、應(yīng)用長(zhǎng)時(shí)間運(yùn)行,沒(méi)有重啟

沒(méi)有重啟 JVM 進(jìn)程一般發(fā)生在調(diào)試時(shí),如下面 tomcat 官網(wǎng)的一個(gè) FAQ:

Why does the memory usage increase when I redeploy a web application?

That is because your web application has a memory leak.

A common issue are “PermGen” memory leaks. They happen because the Classloader (and the Class objects it loaded) cannot be recycled unless some requirements are met (). They are stored in the permanent heap generation by the JVM, and when you redeploy a new class loader is created, which loads another copy of all these classes. This can cause OufOfMemoryErrors eventually.

(*) The requirement is that all classes loaded by this classloader should be able to be gc’ed at the same time.

解決方法

永久代/元空間 溢出的原因比較簡(jiǎn)單,解決方法有如下幾種:

1、檢查是否永久代空間或者元空間設(shè)置的過(guò)小
2、檢查代碼中是否存在大量的反射操作
3、dump之后通過(guò)mat檢查是否存在大量由于反射生成的代理類
4、放大招,重啟JVM

GC overhead limit exceeded

這個(gè)異常比較的罕見(jiàn),報(bào)錯(cuò)信息:

java.lang.OutOfMemoryError:GC overhead limit exceeded

原因

這個(gè)是JDK6新加的錯(cuò)誤類型,一般都是堆太小導(dǎo)致的。
Sun 官方對(duì)此的定義:超過(guò)98%的時(shí)間用來(lái)做GC并且回收了不到2%的堆內(nèi)存時(shí)會(huì)拋出此異常。

解決方法

1、檢查項(xiàng)目中是否有大量的死循環(huán)或有使用大內(nèi)存的代碼,優(yōu)化代碼。
2、添加參數(shù)-XX:-UseGCOverheadLimit 禁用這個(gè)檢查,其實(shí)這個(gè)參數(shù)解決不了內(nèi)存問(wèn)題,只是把錯(cuò)誤的信息延后,最終出現(xiàn) java.lang.OutOfMemoryError: Java heap space。
3、dump內(nèi)存,檢查是否存在內(nèi)存泄露,如果沒(méi)有,加大內(nèi)存。

方法棧溢出

報(bào)錯(cuò)信息:

java.lang.OutOfMemoryError : unable to create new native Thread

原因

出現(xiàn)這種異常,基本上都是創(chuàng)建的了大量的線程導(dǎo)致的,以前碰到過(guò)一次,通過(guò)jstack出來(lái)一共8000多個(gè)線程。

解決方法

1、通過(guò) *-Xss *降低的每個(gè)線程棧大小的容量
2、線程總數(shù)也受到系統(tǒng)空閑內(nèi)存和操作系統(tǒng)的限制,檢查是否該系統(tǒng)下有此限制:

  • /proc/sys/kernel/pid_max
  • /proc/sys/kernel/thread-max
  • max_user_process(ulimit -u)
  • /proc/sys/vm/max_map_count

非常規(guī)溢出

下面這些OOM異常,可能大部分的同學(xué)都沒(méi)有碰到過(guò),但還是需要了解一下

分配超大數(shù)組

報(bào)錯(cuò)信息 :

java.lang.OutOfMemoryError: Requested array size exceeds VM limit

這種情況一般是由于不合理的數(shù)組分配請(qǐng)求導(dǎo)致的,在為數(shù)組分配內(nèi)存之前,JVM 會(huì)執(zhí)行一項(xiàng)檢查。要分配的數(shù)組在該平臺(tái)是否可以尋址(addressable),如果不能尋址(addressable)就會(huì)拋出這個(gè)錯(cuò)誤。

解決方法就是檢查你的代碼中是否有創(chuàng)建超大數(shù)組的地方。

swap區(qū)溢出

報(bào)錯(cuò)信息 :

java.lang.OutOfMemoryError: Out of swap space

這種情況一般是操作系統(tǒng)導(dǎo)致的,可能的原因有:

1、swap 分區(qū)大小分配不足;
2、其他進(jìn)程消耗了所有的內(nèi)存。

解決方案:
1、其它服務(wù)進(jìn)程可以選擇性的拆分出去
2、加大swap分區(qū)大小,或者加大機(jī)器內(nèi)存大小

本地方法溢出

報(bào)錯(cuò)信息 :

java.lang.OutOfMemoryError: stack_trace_with_native_method

本地方法在運(yùn)行時(shí)出現(xiàn)了內(nèi)存分配失敗,和之前的方法棧溢出不同,方法棧溢出發(fā)生在 JVM 代碼層面,而本地方法溢出發(fā)生在JNI代碼或本地方法處。

這個(gè)異常出現(xiàn)的概率極低,就算出現(xiàn),只能通過(guò)操作系統(tǒng)本地工具進(jìn)行診斷,難度有點(diǎn)大,還是放棄為妙。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容