2. GC overhead limit exceeded

2.1 GC overhead limit exceeded 概述

Java運行時環境包含一個內建的垃圾收集線程. 在很多其他編程語言中, 開發者需要手動分配和釋放內存區域, 以保證釋放的內存可以被複用.

但是Java應用只需要分配內存. 只要一個特定的內存空間不再使用, 一個單獨的叫做垃圾收集的縣城會清理內存. GC如何檢測特定內存不再使用的內容超出本文範圍, 但是你可以信任GC可以把這個活做的很好.

java.lang.OutOfMemoryError: GC overhead limit exceeded(GC開銷超過限制)錯誤意味着GC嘗試釋放內存但是卻無法完成任何一件事情. 默認它發生在: JVM在GC中花費超過98%的時間,GC之後, 只有不到2%的堆被釋放.

java.lang.OutOfMemoryError: GC overhead limit exceeded錯誤在以下場景會出現: 你的應用消耗了相當多的可用內存, GC反覆嘗試清理, 但均以失敗告終.

2.2 原因

java.lang.OutOfMemoryError: GC overhead limit exceeded錯誤就是JVM在發信號告訴你: 應用消耗了太多時間在做垃圾收集, 而且還收效甚微. 默認情況的配置是JVM會在以下情況下拋出該錯誤: JVM在GC中花費超過98%的時間,GC之後, 只有不到2%的堆被釋放.

如果這個GC開銷限制不存在,會發生什麼情況? 需要注意的是java.lang.OutOfMemoryError: GC overhead limit exceeded錯誤只有當在幾次GC週期之後, 只有2%的內存被釋放的情況下才會拋出. 這意味着只有很少量的內存GC能夠清理, 而且還會再次被迅速填滿, 導致GC再次開始清理線程. 這形成了一個惡性循環,CPU 100%在忙於GC,沒有實際工作可做. 應用最終用戶面臨着極端響應慢的情況 – 本來幾毫秒就能完成的操作需要花費幾分鐘來完成.

所以, java.lang.OutOfMemoryError: GC overhead limit exceeded消息是一個實現快速失敗(fail-fast)原則的相當好的案例.

2.3 案例

在下列案例中, 我們通過初始化一個Map並且在一個無限循環裏增加一個key-value對到該Map來創建一個GC overhead limit exceeded錯誤:

class Wrapper {
    public static void main(String args[]) throws Exception {
        Map map = System.getProperties();
        Random r = new Random();
        while (true) {
            map.put(r.nextInt(), "value");
        }
    }
}

正如你可能猜到的那樣,這是不可能結束的. 事實上, 當我們使用以下語句運行:
java -Xmx100m -XX:+UseParallelGC Wrapper
我們不就就會看到java.lang.OutOfMemoryError: GC overhead limit exceeded信息. 但是上邊的例子很棘手. 當選擇不同的Java heap size或者不同的GC策略, Mac OS X 10.9.2, Hotspot 1.7.0_45死法各不相同. 例如, 當我用更小的Java heap size運行:
java -Xmx10m -XX:+UseParallelGC Wrapper
該應用會拋出一個更常見的java.lang.OutOfMemoryError: Java heap space消息. 當我使用不同的GC策略運行它, 比如-XX:+UseConcMarkSweepGC-XX:+UseG1GC, 錯誤被默認的異常handler捕獲, 並且在這種情況下,因爲堆耗盡, 堆棧跟蹤甚至不能在異常創建中被填充。

這些變化確實是很好的例子,說明在資源受限的情況下你不能預測你的應用程序將會以怎樣的方式死亡,所以不要以你的期望爲基礎寄託在要完成的具體操作序列上。

2.4 解決辦法

作爲一個半開玩笑的解決方案, 如果你希望避免java.lang.OutOfMemoryError: GC overhead limit exceeded消息, 可以在啓動腳本里增加下列信息:
-XX:-UseGCOverheadLimit

強烈建議不要使用這個參數 – 這只是飲鴆止渴: 最終應用會耗盡內存, 需要修復. 指定這個參數只是用一個更常見的消息java.lang.OutOfMemoryError: Java heap space掩蓋了java.lang.OutOfMemoryError: GC overhead limit exceeded錯誤.

另一種方式(臨時的), 是給JVM進程更多的內存. 再次說明, 加這個很簡單:
java.lang.OutOfMemoryError: Java heap space

在上邊例子中, 給Java進程1GB的heap. 增加這個值會解決GC開銷限制問題如果你的應用先是出現內存不足. 但是如果你希望確保你已經解決了潛在的問題而不是掩蓋java.lang.OutOfMemoryError: GC overhead limit exceeded症狀, 你不應該僅止於此. 對於這種情況, 聯繫我就是最好的方式(@ ̄ー ̄@). 當然你手頭上也有不同的工具可供選擇, 如: profilers和內存dump分析工具. 但要準備好投入大量的時間, 而且要意識到這一點: 工具會對你的Java運行時造成了巨大的開銷,因此它們不適合生產環境使用。

對於這種問題, 其實Dynatrace也會進行告警. 我這有一篇分析的案例供參考.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章