一次OutOfMemoryError: GC overhead limit exceeded

歡醉 發表於 2021-10-15
MIT

現象:

由於需要將mysql表中的過期資料在凌晨定時讀取出過濾後轉入到MongoDB,一個轉換SQL達到百行,而且有幾十個,集中執行後程式反饋異常:

Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: GC overhead limit exceeded

Heap記憶體:1.5G,程式在Docker容器限制使用記憶體2G。

監控到記憶體GC變化:

一次OutOfMemoryError: GC overhead limit exceeded

Heap記憶體佔用驟升至1.2G,然後不停的進行FullGC,而且間隔非常短,從下圖中可以看出PermGen穩定,這也表明讀取的資料由於太大是直接進入了老年代記憶體。

一次OutOfMemoryError: GC overhead limit exceeded

這時候CPU也彪升接近100%

一次OutOfMemoryError: GC overhead limit exceeded

請求訪問時長也加長,異常反饋。

一次OutOfMemoryError: GC overhead limit exceeded

java.lang.OutOfMemoryError: GC overhead limit exceeded 這種情況發生的原因是程式基本上耗盡了所有的可用記憶體, GC 也清理不了。

更準確的說法應該是:執行垃圾收集的時間比例太大,有效的運算量太小。預設情況下,如果GC花費的時間超過 98%,並且GC 回收的記憶體少於 2%,JVM 就會丟擲這個錯誤。

網友的解決建議:

有的人在解決 “java.lang.OutOfMemoryError: GC overhead limit exceeded” 錯誤時,配置了下面的啟動引數:

// 不推薦
-XX:-UseGCOverheadLimit

我告訴你,這是一種完全錯誤的做法。因為 UseGCOverheadLimit 這樣使用並不能真正地解決問題,只能推遲一點 out of memory 錯誤發生的時間,到最後還得進行其他處理。指定這個選項,會將原來的 java.lang.OutOfMemoryError: GC overhead limit exceeded 錯誤掩蓋,變成更常見的 java.lang.OutOfMemoryError: Java heap space 錯誤訊息。

有時候觸發 GC overhead limit 錯誤的原因, 是因為分配給JVM的堆記憶體不足。這種情況下只需要增加堆記憶體大小即可。

在大多數情況下, 增加堆記憶體並不能解決問題。例如程式中存在記憶體洩漏, 增加堆記憶體只能推遲產生 java.lang.OutOfMemoryError: Java heap space 錯誤的時間。

所以,要想從根本上解決問題,則需要排查記憶體分配相關的程式碼。簡單來說,需要搞清楚一下兩點:

  • 哪類物件佔用了最多記憶體?
  • 這些物件是在哪部分程式碼中分配的?