這是本系列的第二篇文章, 相關文章列表:css
Java運行時環境內置了 垃圾收集(GC) 模塊. 上一代的不少編程語言中並無自動內存回收機制, 須要程序員手工編寫代碼來進行內存分配和釋放, 以重複利用堆內存。html
在Java程序中, 只須要關心內存分配就行。若是某塊內存再也不使用, 垃圾收集(Garbage Collection) 模塊會自動執行清理。GC的詳細原理請參考 GC性能優化 系列文章, 通常來講, JVM內置的垃圾收集算法就可以應對絕大多數的業務場景。java
java.lang.OutOfMemoryError: GC overhead limit exceeded 這種狀況發生的緣由是, 程序基本上耗盡了全部的可用內存, GC也清理不了。程序員
JVM拋出 java.lang.OutOfMemoryError: GC overhead limit exceeded 錯誤就是發出了這樣的信號: 執行垃圾收集的時間比例太大, 有效的運算量過小. 默認狀況下, 若是GC花費的時間超過 98%, 而且GC回收的內存少於 2%, JVM就會拋出這個錯誤。算法
注意, java.lang.OutOfMemoryError: GC overhead limit exceeded 錯誤只在連續屢次 GC 都只回收了不到2%的極端狀況下才會拋出。假如不拋出 GC overhead limit
錯誤會發生什麼狀況呢? 那就是GC清理的這麼點內存很快會再次填滿, 迫使GC再次執行. 這樣就造成惡性循環, CPU使用率一直是100%, 而GC卻沒有任何成果. 系統用戶就會看到系統卡死 - 之前只須要幾毫秒的操做, 如今須要好幾分鐘才能完成。編程
這也是一個很好的 快速失敗原則 的案例。安全
如下代碼在無限循環中往 Map 裏添加數據。 這會致使 「GC overhead limit exceeded」 錯誤:ruby
package com.cncounter.rtime; import java.util.Map; import java.util.Random; public class TestWrapper { public static void main(String args[]) throws Exception { Map map = System.getProperties(); Random r = new Random(); while (true) { map.put(r.nextInt(), "value"); } } }
配置JVM參數: -Xmx12m
。執行時產生的錯誤信息以下所示:性能優化
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded at java.util.Hashtable.addEntry(Hashtable.java:435) at java.util.Hashtable.put(Hashtable.java:476) at com.cncounter.rtime.TestWrapper.main(TestWrapper.java:11)
你碰到的錯誤信息不必定就是這個。確實, 咱們執行的JVM參數爲:服務器
java -Xmx12m -XX:+UseParallelGC TestWrapper
很快就看到了 java.lang.OutOfMemoryError: GC overhead limit exceeded 錯誤提示消息。但實際上這個示例是有些坑的. 由於配置不一樣的堆內存大小, 選用不一樣的GC算法, 產生的錯誤信息也不相同。例如,當Java堆內存設置爲10M時:
java -Xmx10m -XX:+UseParallelGC TestWrapper
DEBUG模式下錯誤信息以下所示:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Hashtable.rehash(Hashtable.java:401) at java.util.Hashtable.addEntry(Hashtable.java:425) at java.util.Hashtable.put(Hashtable.java:476) at com.cncounter.rtime.TestWrapper.main(TestWrapper.java:11)
讀者應該試着修改參數, 執行看看具體。錯誤提示以及堆棧信息可能不太同樣。
這裏在 Map 進行 rehash
時拋出了 java.lang.OutOfMemoryError: Java heap space 錯誤消息. 若是使用其餘 垃圾收集算法, 好比 -XX:+UseConcMarkSweepGC, 或者 -XX:+UseG1GC, 錯誤將被默認的 exception handler 所捕獲, 可是沒有 stacktrace 信息, 由於在建立 Exception 時 沒辦法填充stacktrace信息。
例如配置:
-Xmx12m -XX:+UseG1GC
在Win7x64, Java8環境運行, 產生的錯誤信息爲:
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
建議讀者修改內存配置, 以及垃圾收集算法進行測試。
這些真實的案例代表, 在資源受限的狀況下, 沒法準確預測程序會死於哪一種具體的緣由。因此在這類錯誤面前, 不能綁死某種特定的錯誤處理順序。
有一種應付了事的解決方案, 就是不想拋出 「java.lang.OutOfMemoryError: GC overhead limit exceeded」 錯誤信息, 則添加下面啓動參數:
// 不推薦 -XX:-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 錯誤的時間。
固然, 增大堆內存, 還有可能會增長 GC pauses 的時間, 從而影響程序的 吞吐量或延遲。
若是想從根本上解決問題, 則須要排查內存分配相關的代碼. 簡單來講, 須要回答如下問題:
哪類對象佔用了最多內存?
這些對象是在哪部分代碼中分配的。
要搞清這一點, 可能須要好幾天時間。下面是大體的流程:
得到在生產服務器上執行堆轉儲(heap dump)的權限。「轉儲」(Dump)是堆內存的快照, 可用於後續的內存分析. 這些快照中可能含有機密信息, 例如密碼、信用卡帳號等, 因此有時候, 因爲企業的安全限制, 要得到生產環境的堆轉儲並不容易。
在適當的時間執行堆轉儲。通常來講,內存分析須要比對多個堆轉儲文件, 假如獲取的時機不對, 那就多是一個「廢」的快照. 另外, 每執行一次堆轉儲, 就會對JVM進行一次「凍結」, 因此生產環境中,不能執行太多的Dump操做,不然系統緩慢或者卡死,你的麻煩就大了。
用另外一臺機器來加載Dump文件。若是出問題的JVM內存是8GB, 那麼分析 Heap Dump 的機器內存通常須要大於 8GB. 而後打開轉儲分析軟件(咱們推薦Eclipse MAT , 固然你也可使用其餘工具)。
檢測快照中佔用內存最大的 GC roots。詳情請參考: Solving OutOfMemoryError (part 6) – Dump is not a waste。 這對新手來講可能有點困難, 但這也會加深你對堆內存結構以及 navigation 機制的理解。
接下來, 找出可能會分配大量對象的代碼. 若是對整個系統很是熟悉, 可能很快就能定位問題。運氣很差的話,就只有加班加點來進行排查了。
打個廣告, 咱們推薦 Plumbr, the only Java monitoring solution with automatic root cause detection。 Plumbr 能捕獲全部的 java.lang.OutOfMemoryError , 並找出其餘的性能問題, 例如最消耗內存的數據結構等等。
Plumbr 在後臺負責收集數據 —— 包括堆內存使用狀況(只統計對象分佈圖, 不涉及實際數據),以及在堆轉儲中不容易發現的各類問題。 若是發生 java.lang.OutOfMemoryError , 還能在不停機的狀況下, 作必要的數據處理. 下面是Plumbr 對一個 java.lang.OutOfMemoryError 的提醒:
強大吧, 不須要其餘工具和分析, 就能直接看到:
哪類對象佔用了最多的內存(此處是 271 個 com.example.map.impl.PartitionContainer 實例, 消耗了 173MB 內存, 而堆內存只有 248MB)
這些對象在何處建立(大部分是在 MetricManagerImpl 類中,第304行處)
當前是誰在引用這些對象(從 GC root 開始的完整引用鏈)
得知這些信息, 就能夠定位到問題的根源, 例如是當地精簡數據結構/模型, 只佔用必要的內存便可。
固然, 根據內存分析的結果, 以及Plumbr生成的報告, 若是發現對象佔用的內存很合理, 也不須要修改源代碼的話, 那就增大堆內存吧。在這種狀況下,修改JVM啓動參數, (按比例)增長下面的值:
java -Xmx1024m com.yourcompany.YourClass`
這裏配置了最大堆內存爲 1GB
。請根據實際狀況修改這個值. 若是 JVM 仍是會拋出 OutOfMemoryError, 那麼你可能還須要查詢手冊, 或者藉助工具再次進行分析和診斷。
原文連接: https://plumbr.eu/outofmemoryerror/gc-overhead-limit-exceeded
翻譯日期: 2017年8月25日