高手總結的9種 OOM 常見原因及解決方案

2021-02-20 java一日一條

譯者:涯海

https://github.com/StabilityMan/StabilityGuide

當 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 等資源沒有回收。

解決方案

針對大部分情況,通常只需要通過 -Xmx 參數調高 JVM 堆內存空間即可。如果仍然沒有解決,可以參考以下情況做進一步處理:

1、如果是超大對象,可以檢查其合理性,比如是否一次性查詢了資料庫全部結果,而沒有做結果數限制。

2、如果是業務峰值壓力,可以考慮添加機器資源,或者做限流降級。

3、如果是內存洩漏,需要找到持有的對象,修改代碼設計,比如關閉沒有釋放的連接。

2、GC overhead limit exceeded

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

此類問題的原因與解決方案跟 Javaheap space 非常類似,可以參考上文。

3、Permgen space

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

原因分析

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

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

2、常量池;

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

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

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

解決方案

根據 Permgen space 報錯的時機,可以採用不同的解決方案,如下所示:

1、程序啟動報錯,修改 -XX:MaxPermSize 啟動參數,調大永久代空間。

2、應用重新部署時報錯,很可能是沒有應用沒有重啟,導致加載了多份 class 信息,只需重啟 JVM 即可解決。

3、運行時報錯,應用程式可能會動態創建大量 class,而這些 class 的生命周期很短暫,但是 JVM 默認不會卸載 class,可以設置 -XX:+CMSClassUnloadingEnabled 和 -XX:+UseConcMarkSweepGC這兩個參數允許 JVM 卸載 class。

如果上述方法無法解決,可以通過 jmap 命令 dump 內存對象 jmap-dump:format=b,file=dump.hprof<process-id> ,然後利用 Eclipse MAT https://www.eclipse.org/mat 功能逐一分析開銷最大的 classloader 和重複 class。

4、Metaspace

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

此類問題的原因與解決方法跟 Permgenspace 非常類似,可以參考上文。需要特別注意的是調整 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 錯誤。

解決方案

1、升級配置,為機器提供更多的內存;

2、降低 Java Heap Space 大小;

3、修復應用程式的線程洩漏問題;

4、限制線程池大小;

5、使用 -Xss 參數減少線程棧的大小;

6、調高 OS 層面的線程最大數:執行 ulimia-a 查看最大線程數限制,使用 ulimit-u xxx 調整最大線程數限制。

ulimit -a .... 省略部分內容 max user processes (-u) 16384

6、Out of swap space?

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

原因分析

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

1、地址空間不足;

2、物理內存已耗光;

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

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

解決方案

根據錯誤原因可以採取如下解決方案:

1、升級地址空間為 64 bit;

2、使用 Arthas 檢查是否為 Inflater/Deflater 解壓縮問題,如果是,則顯式調用 end 方法。

3、Direct ByteBuffer 問題可以通過啟動參數 -XX:MaxDirectMemorySize 調低閾值。

4、升級伺服器配置/隔離部署,避免爭用。

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

解決方案

1、升級伺服器配置/隔離部署,避免爭用。

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

解決方案

1、Java 只能通過 ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer,因此,可以通過 Arthas 等在線診斷工具攔截該方法進行排查。

2、檢查是否直接或間接使用了 NIO,如 netty,jetty 等。

3、通過啟動參數 -XX:MaxDirectMemorySize 調整 Direct ByteBuffer 的上限值。

4、檢查 JVM 參數是否有 -XX:+DisableExplicitGC 選項,如果有就去掉,因為該參數會使 System.gc() 失效。

5、檢查堆外內存使用代碼,確認是否存在內存洩漏;或者通過反射調用 sun.misc.Cleaner 的 clean() 方法來主動釋放被 Direct ByteBuffer 持有的內存空間。

6、內存容量確實不足,升級配置。

推薦工具&產品JVM 內存分析工具 mat

1、Eclipse Memory Analyzer

https://www.eclipse.org/mat

阿里雲 APM 產品,支持 OOM 異常關鍵字告警

2、ARMS

https://help.aliyun.com/document_detail/42966.html?spm=a2c4g.11174283.6.685.d69b668cuztvff

阿里 Java 在線診斷工具 Arthas(阿爾薩斯)

3、alibaba Arthas

https://github.com/alibaba/arthas

參考文章

1、Plumbr OutOfMemoryError(推薦,含代碼示例)

2、GCeasy OutOfMemoryError

3、JVM 內存結構

相關焦點

  • 9種常見的OOM場景演示!
    1.2 原因分析無限遞歸循環調用(最常見原因),要時刻注意代碼中是否有了循環調用方法而無法退出的情況native 代碼有棧上分配的邏輯,並且要求的內存還不小,比如 java.net.SocketInputStream.read0 會在棧上要求分配一個
  • 教你寫Bug,常見的 OOM 異常分析
    1.2 原因分析無限遞歸循環調用(最常見原因),要時刻注意代碼中是否有了循環調用方法而無法退出的情況native 代碼有棧上分配的邏輯,並且要求的內存還不小,比如 java.net.SocketInputStream.read0 會在棧上要求分配一個
  • 最常見的五種導致應用緩慢的原因及解決方案
    讓我們先來分析一下最常見的五種導致應用緩慢的原因,然後再對症下藥,找到並修復它們吧!  1.客戶端緩慢  問題:當今基於web的應用傾向於將用戶交互工作(通常伴隨大量數據)推送到客戶端工作站。從那裡,JavaScript代碼會處理成百上千行的數據,而這些數據,在客戶端顯示更新前會導致數秒的停頓。
  • 沙發八大常見問題及解決方案
    實踐經驗中,各大檢測機構總結了沙發常見的幾個問題,有利於沙發品質的保障。常見問題1:沙發泡沫塑料理化性能的取樣方式是怎麼樣的?解決方案:經過與多家權威機構確認,結合行業中其他類似的標準,制定如下海綿取樣原則。a).
  • 樓頂發光字閃爍的4大常見原因分析及其對應解決方案
    作為一名標識行業從業者,站在樓頂發光字製作廠家的角度,樓頂發光字的閃爍很有可能是產品出現了故障,按照特定規律閃爍的除外,根據柯賽過去數十年的實際工程案例來看,導致樓頂發光字閃爍不定的情況通常有4種可能的原因。
  • Kubernetes 服務質量和 OOM 詳解
    },- "cgroupsPath": "/kubepods/pod477062c0-1c/05bef2cca07a...",+ "cgroupsPath": "/kubepods/besteffort/pod31b936/1435..."
  • Django常見錯誤信息匯總及解決方案
    小編我前面已經寫過一篇關於Django常見錯誤的文章了,見Django常見錯誤總結: 細數我們一起走過的大坑。
  • 全面解析汙水處理58種常見異常問題及解決方案
    治理水汙染當然離不開汙水處理,汙水處理包含各種處理工藝,不同的工藝手法會產生不同的問題,本文詳細總結了汙水處理工藝58種常見異常問題及解決方案。
  • 10個常見錯誤報告原因及解決方案
    下文是你在eBay上銷售或使用ebay集成應用程式時可能會遇到的所有eBay錯誤報告,以及它們的原因和解決方案,以避免在銷售過程中遇到錯誤報告。常見eBay錯誤報告的原因和解決方案1、Listing違反了重複Listing政策。
  • 懟就完事了,總結幾種驗證碼的解決方案
    於是鹹魚總結了目前遇到過的的驗證碼類型以及總結出來的相關處理方式和大家聊一聊。現在市面上的驗證碼的類型大致有下面幾種類型。圖形驗證碼比較常見的英文數字組合成的圖形驗證碼,常常輔以各類幹擾線和扭曲圖片中的內容達到提高混淆難度的目的,並且通過加長圖片中的文字長度提升識別成本。
  • 值得收藏:主機正常但是顯示器不亮的常見故障以及解決方案!
    找到了原因所在,小編給他重新裝了一個DVID轉VGA的轉接頭,再次點亮就正常了。上面這一種是因為接口沒有找對,但是生活中常遇到的情況,一般來說,桌上型電腦主機正常,顯示器不亮,小編常見的有以下8種故障,如下:第一種:顯示器本身有問題,有故障。
  • 郭健:Linux內存管理系統參數配置之OOM(內存耗盡)
    內核中sysctl_panic_on_oom變量是和/proc/sys/vm/panic_on_oom對應的,主要的判斷邏輯如下:如果萬事俱備,那麼就調用oom_kill_process幹掉當前進程。內核的算法倒是非常簡單,那就是打分(oom_score,注意,該參數是read only的),找到分數最高的就OK了。那麼怎麼來算分數呢?可以參考內核中的oom_badness函數:
  • 樂高EV3常見Bug,Bug的解決方案,附帶搭建機械結構
    EV3傳感器的常見問題及解決方案。陀螺儀傳感器:因為陀螺儀實質是角速度傳感器,由於用角速度來測量角度只能通過積分,所以一旦角速度有一點點的誤差,隨著時間的推移,就會不停地把這個誤差累加起來,這也就是靜止不動時,樂高的所謂陀螺儀角度會自己變化的原因。解決方案:每次用來測角度前,都必須先清0(拔了重插)。
  • 醫用防護服熱熔膠條在使用中常見問題的解決方案
    本篇我們特地整理了醫用防護服熱熔膠條在使用中的常見問題以及解決方案,希望對從事防護服生產的企業能夠有幫助。一般在壓膠過程中出現的問題是:面料不耐溫,壓膠過程中產生破洞、粘補助感覺沒黏性、壓住後第三天又輕鬆開膠。這些原因都會導致壓膠失敗,測試水壓不過關。
  • 矽膠復模生產廠家及常見問題解決方案
    遇到的常見問題:一、尺寸偏差產品變形產生原因及解決方案:跟復模生產工藝特別是模具的溫度控制有很大關係。其他的原因比如原型的縮放比例及材料的縮水也應該控制好。長度超過1米5的產品需要使用加固模具製作.三、成品變脆、開裂產生原因及解決方案:主要是使用了國產劣質材料和復模工藝的缺陷導致,解決辦法首先是確保材料採購的渠道是有保障的,如果材料沒有問題,在工藝上面去改進,注意控制烤箱溫度和加熱時間,冷卻時間,冷卻方式等。
  • 仿石漆施工常見問題(起皮、開裂)解決方案
    5D潔淨隔熱仿石漆效果做外牆仿石漆最常見的問題有幾個:起皮,開裂,翹邊,而引起這些問題的原因,無非是兩個方面,一個方面是用材料不對,另外一個方面就是施工的工藝有問題。1)、產生原因:外牆基層處理一般情況下有幾種材料處理,第一種是常用的外牆膩子,第二種是外牆常用的抗裂砂漿。一般不建議採用外牆膩子進行基層處理,外牆膩子的硬度和結構力相對比較差,容易粉化,採用外牆膩子批刮找平後打磨,也會對膩子層結構產生破壞,表面形成粉塵,容易導致塗層和基層起皮。
  • 高能:10種屎,26種原因分析,19種解決方案
    圖片來自網絡應大家的要求又來談屎了,我們上一篇的屎學研究中並不是細講了所有的大便,今天我們再來總結補充綠便解決方案:不要餵的撐了,注意保暖以上原因均會導致寶寶拉綠便,但是只要寶寶奶量沒有明顯變化,精神好,沒有異常哭鬧行為,肚子不脹,大便不是很稀,體溫正常,就算有奶瓣也是正常的,無需過於擔心。
  • 直播系統中視頻出現卡頓的原因及解決方案
    我們大多數人可能都會認為直播中視頻出現卡頓只是因為網速太差的原因,其實不全然。今天就為大家解析一下,造成直播視頻卡頓的常見的原因:解決方案:我們可對推流器的源碼進行研究,把讀取到的每一幀音頻、視頻的時間戳進行查閱、分析,然後通過程序幹涉推流器或者流媒體伺服器,矯正邏輯值,使音畫同步。
  • RecyclerView 使用總結以及常見問題解決方案
    本文主要講一下我個人對於RecycleView的使用的一些思考以及一些常見的問題怎麼解決。先來看一下使用RecycleView時常見的問題以及一些需求。這個往往是因為你沒有設置LayoutManger。
  • 4種常見食物才是真正的「補鐵高手」,別錯過
    4種常見食物才是真正的「補鐵高手」,別錯過鐵,是人體必需微量元素之一,是血紅蛋白的重要部分,在人體內發揮著重要的生理功能。可你知道嗎,其實菠菜的鐵含量並不高,每100克的新鮮菠菜中只含鐵2.9毫克。而雞蛋黃呢,每100克的含鐵量在6.5毫克。雖然雞蛋黃的鐵含量較多,但是由於雞蛋黃中含有的卵黃高磷蛋白物質會抑制鐵吸收,所以蛋黃中的鐵吸收率很低,只有3%,從這個角度來說,它的補鐵效果不好。