java.lang.OutOfMemoryError GC overhead limit exceeded原因分析及解決方案

  • 2019 年 10 月 29 日
  • 筆記

最近一個上線運行良好的項目出現用戶無法登錄或者執行某個操作時,有卡頓現象。查看了日誌,出現了大量的java.lang.OutOfMemoryError: GC overhead limit exceeded錯誤。

oracle官方給出了這個錯誤產生的原因和解決方法:

Exception in thread thread_name: java.lang.OutOfMemoryError: GC Overhead limit exceeded
Cause: The detail message "GC overhead limit exceeded" indicates that the garbage collector is running all the time and Java program is making very slow progress. After a garbage collection, if the Java process is spending more than approximately 98% of its time doing garbage collection and if it is recovering less than 2% of the heap and has been doing so far the last 5 (compile time constant) consecutive garbage collections, then a java.lang.OutOfMemoryError is thrown. This exception is typically thrown because the amount of live data barely fits into the Java heap having little free space for new allocations.
Action: Increase the heap size. The java.lang.OutOfMemoryError exception for GC Overhead limit exceeded can be turned off with the command line flag -XX:-UseGCOverheadLimit.

原因:
大概意思就是說,JVM花費了98%的時間進行垃圾回收,而只得到2%可用的內存,頻繁的進行內存回收(最起碼已經進行了5次連續的垃圾回收),JVM就會曝出ava.lang.OutOfMemoryError: GC overhead limit exceeded錯誤。

java運行環境包含了一個內置的Garbage Collection (GC)垃圾回收進程,用於對不在使用的內存區域進行回收,釋放被佔用的內存,jvm會根據程序的運行情況,執行GC垃圾回收操作。java語言,程序員只需關注內存的分配,無需關注內存的回收。

而其他大多數的編程語言,卻需要程序員手工編寫分配和釋放內存的代碼。

這種機制也會有一些問題,就是被佔用的內存,經過多次長時間的GC操作都無法回收,導致可用內存越來越少,俗稱內存泄露,JVM就會報java.lang.OutOfMemoryError: GC overhead limit exceeded錯誤。

這個是jdk1.6新增的錯誤類型。

如果沒有這個異常,會出現什麼情況呢?經過垃圾回收釋放的2%可用內存空間會快速的被填滿,迫使GC再次執行,出現頻繁的執行GC操作, 服務器會因為頻繁的執行GC垃圾回收操作而達到100%的時使用率,服務器運行變慢,應用系統會出現卡死現象,平常只需幾毫秒就可以執行的操作,現在需要更長時間,甚至是好幾分鐘才可以完成。

解決方法:
1、增加heap堆內存。
2、增加對內存後錯誤依舊,獲取heap內存快照,使用Eclipse MAT工具,找出內存泄露發生的原因並進行修復。
3、優化代碼以使用更少的內存或重用對象,而不是創建新的對象,從而減少垃圾收集器運行的次數。如果代碼中創建了許多臨時對象(例如在循環中),應該嘗試重用它們。
4、升級JDK到1.8,最起碼也是1.7,並使用G1GC垃圾回收算法。
5、除了使用命令-xms1g -xmx2g設置堆內存之外,嘗試在啟動腳本中加入配置:

-XX:+UseG1GC -XX:G1HeapRegionSize=n -XX:MaxGCPauseMillis=m  -XX:ParallelGCThreads=n -XX:ConcGCThreads=n

還有一個非常不建議使用的解決方法:
在啟動腳本中添加-XX:-UseGCOverheadLimit命令。這個方法只會把「java.lang.OutOfMemoryError: GC overhead limit exceeded」變成更常見的java.lang.OutOfMemoryError: Java heap space錯誤。

我是如何解決這個問題的呢?
首先我的項目是在jdk1.8,64位操作系統上運行,服務器物理內存64G,內存足夠,當時分配的4G的內存,並且運行了穩定運行了一年,沒有出現過內存溢出的問題。

所以我判斷是內存泄漏,內存泄露很隱秘,基本是代碼的原因,有大量的對象佔用內存,又不能被GC回收,久而久之就出現內存不足,無法給新建的對象分配空間,曝出GC overhead limit exceeded。

經過分析內存,找出原因所在,有段代碼使用while循環,不停的new對象,佔用了大量的內存,修改代碼之後,問題即解決。

GC overhead limit exceeded問題歸根結底還是代碼的問題,和內存無關,代碼中出現了大量佔用內存的對象。