1. 程式人生 > >java.lang.OutOfMemoryError GC overhead limit exceeded原因分析及解決方案

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

最近一個上線執行良好的專案出現使用者無法登入或者執行某個操作時,有卡頓現象。查看了日誌,出現了大量的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問題歸根結底還是程式碼的問題,和記憶體無關,程式碼中出現了大量佔用記憶體的物件