OOM 常見(jiàn)原因及解決方案

一 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)原因可以分為以下幾類:

  1. 請(qǐng)求創(chuàng)建一個(gè)超大對(duì)象鸿竖,通常是一個(gè)大數(shù)組。
  2. 超出預(yù)期的訪問(wèn)量/數(shù)據(jù)量铸敏,通常是上游系統(tǒng)請(qǐng)求流量飆升缚忧,常見(jiàn)于各類促銷(xiāo)/秒殺活動(dòng),可以結(jié)合業(yè)務(wù)流量指標(biāo)排查是否有尖狀峰值杈笔。
  3. 過(guò)度使用終結(jié)器(Finalizer)闪水,該對(duì)象沒(méi)有立即被 GC。
  4. 內(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)一步處理:

  1. 如果是超大對(duì)象右钾,可以檢查其合理性蚁吝,比如是否一次性查詢了數(shù)據(jù)庫(kù)全部結(jié)果,而沒(méi)有做結(jié)果數(shù)限制舀射。
  2. 如果是業(yè)務(wù)峰值壓力窘茁,可以考慮添加機(jī)器資源,或者做限流降級(jí)脆烟。
  3. 如果是內(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ì)象主要包括以下幾類:

  1. 加載/緩存到內(nèi)存中的 class 定義,包括類的名稱咙边,字段猜煮,方法和字節(jié)碼;
  2. 常量池败许;
  3. 對(duì)象數(shù)組/類型數(shù)組所關(guān)聯(lián)的 class王带;
  4. JIT 編譯器優(yōu)化后的 class 信息。

解決方案

根據(jù) Permgen space 報(bào)錯(cuò)的時(shí)機(jī)市殷,可以采用不同的解決方案愕撰,如下所示:

  1. 程序啟動(dòng)報(bào)錯(cuò),修改 -XX:MaxPermSize 啟動(dòng)參數(shù),調(diào)大永久代空間搞挣。
  2. 應(yīng)用重新部署時(shí)報(bào)錯(cuò)带迟,很可能是沒(méi)有應(yīng)用沒(méi)有重啟,導(dǎo)致加載了多份 class 信息囱桨,只需重啟 JVM 即可解決仓犬。
  3. 運(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)的原因包括以下幾類:

  1. 線程數(shù)超過(guò)操作系統(tǒng)最大線程數(shù) ulimit 限制甘磨;
  2. 線程數(shù)超過(guò) kernel.pid_max(只能重啟);
  3. native 內(nèi)存不足眯停;

該問(wèn)題發(fā)生的常見(jiàn)過(guò)程主要包括以下幾步:

  1. JVM 內(nèi)部的應(yīng)用程序請(qǐng)求創(chuàng)建一個(gè)新的 Java 線程济舆;
  2. JVM native 方法代理了該次請(qǐng)求,并向操作系統(tǒng)請(qǐng)求創(chuàng)建一個(gè) native 線程莺债;
  3. 操作系統(tǒng)嘗試創(chuàng)建一個(gè)新的 native 線程滋觉,并為其分配內(nèi)存;
  4. 如果操作系統(tǒng)的虛擬內(nèi)存已耗盡齐邦,或是受到 32 位進(jìn)程的地址空間限制椎侠,操作系統(tǒng)就會(huì)拒絕本次 native 內(nèi)存分配;
  5. JVM 將拋出 java.lang.OutOfMemoryError:Unableto createnewnativethread 錯(cuò)誤措拇。

解決方案

  1. 升級(jí)配置我纪,為機(jī)器提供更多的內(nèi)存;
  2. 降低 Java Heap Space 大小浅悉;
  3. 修復(fù)應(yīng)用程序的線程泄漏問(wèn)題趟据;
  4. 限制線程池大小术健;
  5. 使用 -Xss 參數(shù)減少線程棧的大行诩睢;
  6. 調(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)原因包括以下幾類:

  1. 地址空間不足;
  2. 物理內(nèi)存已耗光娇昙;
  3. 應(yīng)用程序的本地內(nèi)存泄漏(native leak)尺迂,例如不斷申請(qǐng)本地內(nèi)存,卻不釋放冒掌。
  4. 執(zhí)行 jmap-histo:live<pid> 命令噪裕,強(qiáng)制執(zhí)行 Full GC;如果幾次執(zhí)行后內(nèi)存明顯下降股毫,則基本確認(rèn)為 Direct ByteBuffer 問(wèn)題膳音。

解決方案

根據(jù)錯(cuò)誤原因可以采取如下解決方案:

  1. 升級(jí)地址空間為 64 bit;
  2. 使用 Arthas 檢查是否為 Inflater/Deflater 解壓縮問(wèn)題铃诬,如果是祭陷,則顯式調(diào)用 end 方法。
  3. Direct ByteBuffer 問(wèn)題可以通過(guò)啟動(dòng)參數(shù) -XX:MaxDirectMemorySize 調(diào)低閾值趣席。
  4. 升級(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)存資源酌摇。

解決方案

  1. 升級(jí)服務(wù)器配置/隔離部署膝舅,避免爭(zhēng)用。
  2. 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ò)誤蛀蜜。

解決方案

  1. Java 只能通過(guò) ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer,因此增蹭,可以通過(guò) Arthas 等在線診斷工具攔截該方法進(jìn)行排查。
  2. 檢查是否直接或間接使用了 NIO磅摹,如 netty滋迈,jetty 等。
  3. 通過(guò)啟動(dòng)參數(shù) -XX:MaxDirectMemorySize 調(diào)整 Direct ByteBuffer 的上限值户誓。
  4. 檢查 JVM 參數(shù)是否有 -XX:+DisableExplicitGC 選項(xiàng)饼灿,如果有就去掉,因?yàn)樵搮?shù)會(huì)使 System.gc() 失效帝美。
  5. 檢查堆外內(nèi)存使用代碼碍彭,確認(rèn)是否存在內(nèi)存泄漏;或者通過(guò)反射調(diào)用 sun.misc.Cleanerclean() 方法來(lái)主動(dòng)釋放被 Direct ByteBuffer 持有的內(nèi)存空間。
  6. 內(nèi)存容量確實(shí)不足庇忌,升級(jí)配置舞箍。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市皆疹,隨后出現(xiàn)的幾起案子疏橄,更是在濱河造成了極大的恐慌,老刑警劉巖略就,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捎迫,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡表牢,警方通過(guò)查閱死者的電腦和手機(jī)窄绒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)崔兴,“玉大人彰导,你說(shuō)我怎么就攤上這事∧詹迹” “怎么了螺戳?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)折汞。 經(jīng)常有香客問(wèn)我倔幼,道長(zhǎng),這世上最難降的妖魔是什么爽待? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任损同,我火速辦了婚禮,結(jié)果婚禮上鸟款,老公的妹妹穿的比我還像新娘膏燃。我一直安慰自己,他們只是感情好何什,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布组哩。 她就那樣靜靜地躺著,像睡著了一般处渣。 火紅的嫁衣襯著肌膚如雪伶贰。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,071評(píng)論 1 285
  • 那天罐栈,我揣著相機(jī)與錄音黍衙,去河邊找鬼。 笑死荠诬,一個(gè)胖子當(dāng)著我的面吹牛琅翻,可吹牛的內(nèi)容都是我干的位仁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼方椎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼聂抢!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起辩尊,我...
    開(kāi)封第一講書(shū)人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涛浙,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后摄欲,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體轿亮,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年胸墙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了我注。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡迟隅,死狀恐怖但骨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情智袭,我是刑警寧澤奔缠,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站吼野,受9級(jí)特大地震影響校哎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瞳步,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一闷哆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧单起,春花似錦抱怔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至测蘑,卻和暖如春灌危,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帮寻。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赠摇,地道東北人固逗。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓浅蚪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親烫罩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子惜傲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容