一 Java heap space
當(dāng)堆內(nèi)存(Heap Space)沒(méi)有足夠空間存放新創(chuàng)建的對(duì)象時(shí),就會(huì)拋出 java.lang.OutOfMemoryError:Javaheap space
錯(cuò)誤(根據(jù)實(shí)際生產(chǎn)經(jīng)驗(yàn)谭梗,可以對(duì)程序日志中的 OutOfMemoryError 配置關(guān)鍵字告警屉佳,一經(jīng)發(fā)現(xiàn),立即處理)斜姥。
原因分析
Javaheap space
錯(cuò)誤產(chǎn)生的常見(jiàn)原因可以分為以下幾類:
- 請(qǐng)求創(chuàng)建一個(gè)超大對(duì)象鸿竖,通常是一個(gè)大數(shù)組。
- 超出預(yù)期的訪問(wèn)量/數(shù)據(jù)量铸敏,通常是上游系統(tǒng)請(qǐng)求流量飆升缚忧,常見(jiàn)于各類促銷(xiāo)/秒殺活動(dòng),可以結(jié)合業(yè)務(wù)流量指標(biāo)排查是否有尖狀峰值杈笔。
- 過(guò)度使用終結(jié)器(Finalizer)闪水,該對(duì)象沒(méi)有立即被 GC。
- 內(nèi)存泄漏(Memory Leak)蒙具,大量對(duì)象引用沒(méi)有釋放球榆,JVM 無(wú)法對(duì)其自動(dòng)回收朽肥,常見(jiàn)于使用了 File 等資源沒(méi)有回收。
解決方案
針對(duì)大部分情況芜果,通常只需要通過(guò) -Xmx
參數(shù)調(diào)高 JVM 堆內(nèi)存空間即可鞠呈。如果仍然沒(méi)有解決,可以參考以下情況做進(jìn)一步處理:
- 如果是超大對(duì)象右钾,可以檢查其合理性蚁吝,比如是否一次性查詢了數(shù)據(jù)庫(kù)全部結(jié)果,而沒(méi)有做結(jié)果數(shù)限制舀射。
- 如果是業(yè)務(wù)峰值壓力窘茁,可以考慮添加機(jī)器資源,或者做限流降級(jí)脆烟。
- 如果是內(nèi)存泄漏山林,需要找到持有的對(duì)象,修改代碼設(shè)計(jì)邢羔,比如關(guān)閉沒(méi)有釋放的連接驼抹。
二 GC overhead limit exceeded
java.lang.OutOfMemoryError:GC overhead limit exceeded:當(dāng) Java 進(jìn)程花費(fèi) 98% 以上的時(shí)間執(zhí)行 GC,但只恢復(fù)了不到 2% 的內(nèi)存拜鹤,且該動(dòng)作連續(xù)重復(fù)了 5 次框冀,就會(huì)拋出該錯(cuò)誤。簡(jiǎn)單地說(shuō)敏簿,就是應(yīng)用程序已經(jīng)基本耗盡了所有可用內(nèi)存明也, GC 也無(wú)法回收。
此類問(wèn)題的原因與解決方案跟 Javaheap space
非常類似惯裕,可以參考上文温数。
三 PermGen space
java.lang.OutOfMemoryError: PermGen space:該錯(cuò)誤表示永久代(Permanent Generation)已用滿,通常是因?yàn)榧虞d的 class 數(shù)目太多或體積太大蜻势。
原因分析
PermGen 的使用量與加載到內(nèi)存的 class 的數(shù)量/大小正相關(guān)撑刺。永久代存儲(chǔ)對(duì)象主要包括以下幾類:
- 加載/緩存到內(nèi)存中的 class 定義,包括類的名稱咙边,字段猜煮,方法和字節(jié)碼;
- 常量池败许;
- 對(duì)象數(shù)組/類型數(shù)組所關(guān)聯(lián)的 class王带;
- JIT 編譯器優(yōu)化后的 class 信息。
解決方案
根據(jù) Permgen space 報(bào)錯(cuò)的時(shí)機(jī)市殷,可以采用不同的解決方案愕撰,如下所示:
- 程序啟動(dòng)報(bào)錯(cuò),修改
-XX:MaxPermSize
啟動(dòng)參數(shù),調(diào)大永久代空間搞挣。 - 應(yīng)用重新部署時(shí)報(bào)錯(cuò)带迟,很可能是沒(méi)有應(yīng)用沒(méi)有重啟,導(dǎo)致加載了多份 class 信息囱桨,只需重啟 JVM 即可解決仓犬。
- 運(yùn)行時(shí)報(bào)錯(cuò),應(yīng)用程序可能會(huì)動(dòng)態(tài)創(chuàng)建大量 class舍肠,而這些 class 的生命周期很短暫搀继,但是 JVM 默認(rèn)不會(huì)卸載 class,可以設(shè)置
-XX:+CMSClassUnloadingEnabled
和-XX:+UseConcMarkSweepGC
這兩個(gè)參數(shù)允許 JVM 卸載 class翠语。
如果上述方法無(wú)法解決叽躯,可以通過(guò) jmap 命令 dump 內(nèi)存對(duì)象 jmap-dump:format=b,file=dump.hprof <process-id>
,然后利用 Eclipse MAT https://www.eclipse.org/mat 功能逐一分析開(kāi)銷(xiāo)最大的 classloader 和重復(fù) class肌括。
四 Metaspace
java.lang.OutOfMemoryError: Metaspace:JDK 1.8 使用 Metaspace 替換了永久代(Permanent Generation)点骑,該錯(cuò)誤表示 Metaspace 已被用滿,通常是因?yàn)榧虞d的 class 數(shù)目太多或體積太大谍夭。
此類問(wèn)題的原因與解決方法跟 Permgenspace
非常類似黑滴,可以參考上文。需要特別注意的是調(diào)整 Metaspace 空間大小的啟動(dòng)參數(shù)為 -XX:MaxMetaspaceSize
紧索。
五 Unable to create new native thread
java.lang.OutOfMemoryError: Unable to create new native thread :每個(gè) Java 線程都需要占用一定的內(nèi)存空間跷跪,當(dāng) JVM 向底層操作系統(tǒng)請(qǐng)求創(chuàng)建一個(gè)新的 native 線程時(shí),如果沒(méi)有足夠的資源分配就會(huì)報(bào)此類錯(cuò)誤齐板。
原因分析
JVM 向 OS 請(qǐng)求創(chuàng)建 native 線程失敗,就會(huì)拋出 Unableto createnewnativethread
葛菇,常見(jiàn)的原因包括以下幾類:
- 線程數(shù)超過(guò)操作系統(tǒng)最大線程數(shù) ulimit 限制甘磨;
- 線程數(shù)超過(guò) kernel.pid_max(只能重啟);
- native 內(nèi)存不足眯停;
該問(wèn)題發(fā)生的常見(jiàn)過(guò)程主要包括以下幾步:
- JVM 內(nèi)部的應(yīng)用程序請(qǐng)求創(chuàng)建一個(gè)新的 Java 線程济舆;
- JVM native 方法代理了該次請(qǐng)求,并向操作系統(tǒng)請(qǐng)求創(chuàng)建一個(gè) native 線程莺债;
- 操作系統(tǒng)嘗試創(chuàng)建一個(gè)新的 native 線程滋觉,并為其分配內(nèi)存;
- 如果操作系統(tǒng)的虛擬內(nèi)存已耗盡齐邦,或是受到 32 位進(jìn)程的地址空間限制椎侠,操作系統(tǒng)就會(huì)拒絕本次 native 內(nèi)存分配;
- JVM 將拋出
java.lang.OutOfMemoryError:Unableto createnewnativethread
錯(cuò)誤措拇。
解決方案
- 升級(jí)配置我纪,為機(jī)器提供更多的內(nèi)存;
- 降低 Java Heap Space 大小浅悉;
- 修復(fù)應(yīng)用程序的線程泄漏問(wèn)題趟据;
- 限制線程池大小术健;
- 使用 -Xss 參數(shù)減少線程棧的大行诩睢;
- 調(diào)高 OS 層面的線程最大數(shù):執(zhí)行
ulimit -a
查看最大線程數(shù)限制荞估,使用ulimit-u xxx
調(diào)整最大線程數(shù)限制咳促。
ulimit -a .... 省略部分內(nèi)容 ..... max user processes (-u) 16384
六 Out of swap space
java.lang.OutOfMemoryError: Out of swap space:該錯(cuò)誤表示所有可用的虛擬內(nèi)存已被耗盡。虛擬內(nèi)存(Virtual Memory)由物理內(nèi)存(Physical Memory)和交換空間(Swap Space)兩部分組成泼舱。當(dāng)運(yùn)行時(shí)程序請(qǐng)求的虛擬內(nèi)存溢出時(shí)就會(huì)報(bào) Outof swap space?
錯(cuò)誤等缀。
原因分析
該錯(cuò)誤出現(xiàn)的常見(jiàn)原因包括以下幾類:
- 地址空間不足;
- 物理內(nèi)存已耗光娇昙;
- 應(yīng)用程序的本地內(nèi)存泄漏(native leak)尺迂,例如不斷申請(qǐng)本地內(nèi)存,卻不釋放冒掌。
- 執(zhí)行
jmap-histo:live<pid>
命令噪裕,強(qiáng)制執(zhí)行 Full GC;如果幾次執(zhí)行后內(nèi)存明顯下降股毫,則基本確認(rèn)為 Direct ByteBuffer 問(wèn)題膳音。
解決方案
根據(jù)錯(cuò)誤原因可以采取如下解決方案:
- 升級(jí)地址空間為 64 bit;
- 使用 Arthas 檢查是否為 Inflater/Deflater 解壓縮問(wèn)題铃诬,如果是祭陷,則顯式調(diào)用 end 方法。
- Direct ByteBuffer 問(wèn)題可以通過(guò)啟動(dòng)參數(shù)
-XX:MaxDirectMemorySize
調(diào)低閾值趣席。 - 升級(jí)服務(wù)器配置/隔離部署兵志,避免爭(zhēng)用。
七 Kill process or sacrifice child
Out of memory: kill process or sacrifice child:有一種內(nèi)核作業(yè)(Kernel Job)名為 Out of Memory Killer宣肚,它會(huì)在可用內(nèi)存極低的情況下“殺死”(kill)某些進(jìn)程想罕。OOM Killer 會(huì)對(duì)所有進(jìn)程進(jìn)行打分,然后將評(píng)分較低的進(jìn)程“殺死”霉涨,具體的評(píng)分規(guī)則可以參考 Surviving the Linux OOM Killer按价。
不同于其他的 OOM 錯(cuò)誤, Kill process or sacrifice child
錯(cuò)誤不是由 JVM 層面觸發(fā)的笙瑟,而是由操作系統(tǒng)層面觸發(fā)的楼镐。
原因分析
默認(rèn)情況下,Linux 內(nèi)核允許進(jìn)程申請(qǐng)的內(nèi)存總量大于系統(tǒng)可用內(nèi)存往枷,通過(guò)這種“錯(cuò)峰復(fù)用”的方式可以更有效的利用系統(tǒng)資源鸠蚪。
然而今阳,這種方式也會(huì)無(wú)可避免地帶來(lái)一定的“超賣(mài)”風(fēng)險(xiǎn)。例如某些進(jìn)程持續(xù)占用系統(tǒng)內(nèi)存茅信,然后導(dǎo)致其他進(jìn)程沒(méi)有可用內(nèi)存盾舌。此時(shí),系統(tǒng)將自動(dòng)激活 OOM Killer蘸鲸,尋找評(píng)分低的進(jìn)程妖谴,并將其“殺死”,釋放內(nèi)存資源酌摇。
解決方案
- 升級(jí)服務(wù)器配置/隔離部署膝舅,避免爭(zhēng)用。
- OOM Killer 調(diào)優(yōu)窑多。
八 Requested array size exceeds VM limit
java.lang.OutOfMemoryError: Requested array size exceeds VM limit:這個(gè)錯(cuò)誤是由JVM中的本地代碼拋出的. 在真正為數(shù)組分配內(nèi)存之前, JVM會(huì)執(zhí)行一項(xiàng)檢查: 要分配的數(shù)據(jù)結(jié)構(gòu)在該平臺(tái)是否可以尋址(addressable). 當(dāng)然, 這個(gè)錯(cuò)誤比你所想的還要少見(jiàn)得多仍稀。JVM 限制了數(shù)組的最大長(zhǎng)度,該錯(cuò)誤表示程序請(qǐng)求創(chuàng)建的數(shù)組超過(guò)最大長(zhǎng)度限制埂息。
- JVM 在為數(shù)組分配內(nèi)存前技潘,會(huì)檢查要分配的數(shù)據(jù)結(jié)構(gòu)在系統(tǒng)中是否可尋址,通常為
Integer.MAX_VALUE-2
千康。 - 此類問(wèn)題比較罕見(jiàn)享幽,通常需要檢查代碼,確認(rèn)業(yè)務(wù)是否需要?jiǎng)?chuàng)建如此大的數(shù)組拾弃,是否可以拆分為多個(gè)塊值桩,分批執(zhí)行。
示例
public class OomV2 {
public static void main(String[] args) {
for (int i = 3; i >= 0; i--) {
try {
int[] arr = new int[Integer.MAX_VALUE-i];
System.out.format("Successfully initialized an array with %,d elements.\n", Integer.MAX_VALUE-i);
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
九 Direct buffer memory
java.lang.OutOfMemoryError: Direct buffer memory:Java 允許應(yīng)用程序通過(guò) Direct ByteBuffer 直接訪問(wèn)堆外內(nèi)存豪椿,許多高性能程序通過(guò) Direct ByteBuffer 結(jié)合內(nèi)存映射文件(Memory Mapped File)實(shí)現(xiàn)高速 IO奔坟。
原因分析
Direct ByteBuffer 的默認(rèn)大小為 64 MB,一旦使用超出限制搭盾,就會(huì)拋出 Directbuffer memory
錯(cuò)誤蛀蜜。
解決方案
- Java 只能通過(guò) ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer,因此增蹭,可以通過(guò) Arthas 等在線診斷工具攔截該方法進(jìn)行排查。
- 檢查是否直接或間接使用了 NIO磅摹,如 netty滋迈,jetty 等。
- 通過(guò)啟動(dòng)參數(shù)
-XX:MaxDirectMemorySize
調(diào)整 Direct ByteBuffer 的上限值户誓。 - 檢查 JVM 參數(shù)是否有
-XX:+DisableExplicitGC
選項(xiàng)饼灿,如果有就去掉,因?yàn)樵搮?shù)會(huì)使System.gc()
失效帝美。 - 檢查堆外內(nèi)存使用代碼碍彭,確認(rèn)是否存在內(nèi)存泄漏;或者通過(guò)反射調(diào)用
sun.misc.Cleaner
的clean()
方法來(lái)主動(dòng)釋放被 Direct ByteBuffer 持有的內(nèi)存空間。 - 內(nèi)存容量確實(shí)不足庇忌,升級(jí)配置舞箍。