常見OOM問題之GC overhead limit exceeded 問題詳解

本文來自于HeapDump性能社區(qū)! !有性能問題,上HeapDump性能社區(qū)!

正文

Java 運行時環(huán)境包含一個內(nèi)置的垃圾回收 (GC)進(jìn)程。在許多其他編程語言中,開發(fā)人員需要手動分配和釋放內(nèi)存區(qū)域,以便可以重用釋放的內(nèi)存。

另一方面,Java 應(yīng)用程序只需要分配內(nèi)存。每當(dāng)內(nèi)存中的特定空間不再使用時,稱為垃圾收集的單獨進(jìn)程會為它們清除內(nèi)存。垃圾收集手冊中更詳細(xì)地解釋了 GC 如何檢測內(nèi)存的特定部分,但您可以相信 GC 能很好地完成它的工作。

GC開銷超過極限:java.lang.OutOfMemoryError時顯示錯誤您的應(yīng)用程序已經(jīng)耗盡了幾乎所有的可用內(nèi)存和GC一再未能清除它。

1,是什么原因造成的?

java.lang.OutOfMemoryError:GC開銷超過極限誤差信號,你的應(yīng)用程序花費太多的時間做垃圾收集太少的結(jié)果JVM的方式。默認(rèn)情況下,如果 JVM 花費超過98% 的總時間進(jìn)行 GC 并且在 GC 之后僅回收不到 2% 的堆,則JVM 被配置為拋出此錯誤。

1.png

如果這個 GC 開銷限制不存在,會發(fā)生什么?請注意java.lang.OutOfMemoryError: GC 開銷限制超出錯誤僅在幾次GC 循環(huán)后釋放 2% 的內(nèi)存時才會拋出。這意味著 GC 能夠清理的少量堆可能會再次被快速填滿,從而迫使 GC 再次重新啟動清理過程。這形成了一個惡性循環(huán),CPU 100% 忙于 GC,無法完成任何實際工作。應(yīng)用程序的最終用戶面臨極端的減速——通常在幾毫秒內(nèi)完成的操作需要幾分鐘才能完成。

因此,“ java.lang.OutOfMemoryError: GC 開銷限制超出”消息是快速失敗原則的一個很好的例子。

2,舉個例子

在以下示例中,我們通過初始化 Map 并在未終止的循環(huán)中將鍵值對添加到映射中來創(chuàng)建“超出 GC 開銷限制”錯誤:

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");
    }
  }

正如您可能猜到的那樣,這不會有好的結(jié)局。事實上,當(dāng)我們啟動上述程序時:

java -Xmx100m -XX:+UseParallelGC Wrapper

我們很快就會遇到java.lang.OutOfMemoryError: GC 開銷限制超出消息。但是上面的例子很棘手。當(dāng)使用不同的 Java 堆大小或不同的GC 算法啟動時,我的 Mac OS X 10.9.2 和 Hotspot 1.7.0_45 將選擇不同的死亡。例如,當(dāng)我以較小的 Java 堆大小運行程序時,如下所示:

java -Xmx10m -XX:+UseParallelGC Wrapper

應(yīng)用程序?qū)⒁蚋R姷?em>java.lang.OutOfMemoryError: Java heap space消息而死亡,該消息在 Map resize 時拋出。當(dāng)我使用除ParallelGC之外的其他垃圾收集算法運行它時,例如-XX:+UseConcMarkSweepGC-XX:+UseG1GC,錯誤被默認(rèn)異常處理程序捕獲并且沒有堆棧跟蹤,因為堆已經(jīng)耗盡到甚至無法在異常創(chuàng)建時填充堆棧跟蹤。

這些變化確實是很好的例子,表明在資源受限的情況下,您無法預(yù)測應(yīng)用程序的死亡方式,因此不要將您的期望建立在要完成的特定操作序列上。

3,解決辦法是什么?

作為一個詼諧的解決方案,如果您只是想擺脫“ java.lang.OutOfMemoryError:GC開銷限制超出”消息,將以下內(nèi)容添加到您的啟動腳本中即可實現(xiàn):

-XX:-UseGCOverheadLimit

強(qiáng)烈建議不要使用這個選項——而不是解決問題,你只是推遲不可避免的問題:應(yīng)用程序內(nèi)存不足,需要修復(fù)。指定此選項只會用更熟悉的消息java.lang.OutOfMemoryError: Java heap space掩蓋原始java.lang.OutOfMemoryError: GC 開銷限制超出錯誤。

更嚴(yán)重的一點是 - 有時會觸發(fā) GC 開銷限制錯誤,因為您分配給 JVM 的堆數(shù)量不足以滿足在該 JVM 上運行的應(yīng)用程序的需求。在這種情況下,你應(yīng)該只分配更多的堆——請參閱本章末尾以了解如何實現(xiàn)這一點。

然而,在許多情況下,提供更多的 Java 堆空間并不能解決問題。例如,如果您的應(yīng)用程序包含內(nèi)存泄漏,添加更多堆只會推遲java.lang.OutOfMemoryError: Java heap space錯誤。此外,增加 Java 堆空間量也往往會增加影響應(yīng)用程序吞吐量或延遲GC 暫停時間。

如果您希望解決 Java 堆空間的潛在問題而不是掩蓋癥狀,您需要弄清楚代碼的哪一部分負(fù)責(zé)分配最多的內(nèi)存。換句話說,您需要回答以下問題:

  1. 哪些對象占據(jù)堆的大部分
  2. 在源代碼中分配這些對象的位置

此時,請確保在您的日歷中清除幾天(或 – 請參閱項目符號列表下方的自動方式)。以下是一個粗略的流程大綱,可以幫助您回答上述問題:

  • 從您的 JVM-to-troubleshoot 獲取獲取堆轉(zhuǎn)儲的許可?!稗D(zhuǎn)儲”基本上是您可以分析的堆內(nèi)容的快照,并包含應(yīng)用程序在轉(zhuǎn)儲時保留在內(nèi)存中的所有內(nèi)容。包括密碼、信用卡號等。
  • 指示您的 JVM 將其堆內(nèi)存的內(nèi)容轉(zhuǎn)儲到一個文件中。準(zhǔn)備好進(jìn)行一些轉(zhuǎn)儲,因為在錯誤的時間進(jìn)行時,堆轉(zhuǎn)儲包含大量噪音,實際上可能毫無用處。另一方面,每個堆轉(zhuǎn)儲都會完全“凍結(jié)”JVM,所以不要太多,否則你的最終用戶會開始發(fā)誓。
  • 找到一臺可以加載轉(zhuǎn)儲的機(jī)器。當(dāng)您的 JVM-to-troubleshoot 使用例如 8GB 的堆時,您需要一臺超過 8GB 的機(jī)器來分析堆內(nèi)容。啟動轉(zhuǎn)儲分析軟件(我們推薦Eclipse MAT,但也有同樣好的替代品)。
  • 檢測到最大堆消耗者的 GC 根的路徑。我們已經(jīng)覆蓋在一個單獨的后這一活動在這里。不用擔(dān)心,一開始會覺得很麻煩,但經(jīng)過幾天的挖掘,你會好起來的。
  • 接下來,您需要弄清楚在源代碼中的何處分配了具有潛在危險的大量對象。如果您對應(yīng)用程序的源代碼有很好的了解,則希望通過幾次搜索就能做到這一點。當(dāng)您運氣不佳時,您將需要一些能量飲料來輔助。

或者,我們建議Plumbr,這是唯一具有自動根本原因檢測功能的 Java 監(jiān)控解決方案。在其他性能問題中,它捕獲所有java.lang.OutOfMemoryError并自動為您提供有關(guān)最需要內(nèi)存的數(shù)據(jù)結(jié)構(gòu)的信息。它負(fù)責(zé)在幕后收集必要的數(shù)據(jù)——這包括有關(guān)堆使用情況的相關(guān)數(shù)據(jù)(只有對象布局圖,沒有實際數(shù)據(jù)),以及一些您甚至在堆轉(zhuǎn)儲中都找不到的數(shù)據(jù)。它還會為您進(jìn)行必要的數(shù)據(jù)處理——在 JVM 遇到java.lang.OutOfMemoryError 時立即進(jìn)行。這是來自 Plumbr 的java.lang.OutOfMemoryError事件警報示例:

2.png

無需任何額外的工具或分析,您就可以看到:

  • 哪些對象消耗的內(nèi)存最多(271 個com.example.map.impl.PartitionContainer實例消耗了 248MB 總堆中的 173MB)
  • 這些對象的分配位置(大部分分配在MetricManagerImpl類中,第 304 行)
  • 當(dāng)前引用這些對象的是什么(直到 GC 根的完整引用鏈)

有了這些信息,您就可以放大潛在的根本原因,并確保將數(shù)據(jù)結(jié)構(gòu)修剪到適合您的內(nèi)存池的級別。

但是,當(dāng)您從內(nèi)存分析或閱讀 Plumbr 報告得出的結(jié)論是內(nèi)存使用是合法的并且源代碼中沒有任何更改時,您需要讓您的 JVM 有更多的 Java 堆空間才能正常運行。在這種情況下,更改您的 JVM 啟動配置并在您的啟動腳本中添加(或增加值,如果存在)僅一個參數(shù):

java -Xmx1024m com.yourcompany.YourClass

在上面的例子中,Java 進(jìn)程被分配了 1GB 的堆。修改最適合您的 JVM 的值。但是,如果結(jié)果是您的 JVM 仍然因 OutOfMemoryError 而死亡,您可能仍然無法避免上述手動或 Plumbr 輔助分析。

Java OOM系列專題:

第一篇:Java OOM 原理篇 : 什么是 Java OOM

第二篇:Java OOM 基礎(chǔ)篇:常見的OutOfMemoryError 場景一:Java heap space 堆溢出問題詳解

第三篇:Java OOM 基礎(chǔ)篇:常見的OutOfMemoryError 場景二 : GC overhead limit exceeded 問題詳解

第四篇:Java OOM 基礎(chǔ)篇:常見的OutOfMemoryError 場景三: PermGen space 永久空間問題詳解

第五篇:Java OOM 基礎(chǔ)篇:常見的OutOfMemoryError 場景四: Permgen size 元空間問題詳解

第六篇:Java OOM 實戰(zhàn)篇:應(yīng)用故障之Java heap space 堆溢出實戰(zhàn)

第七篇:Java OOM 高級篇:體驗了一把線上CPU100%及應(yīng)用OOM的排查和解決過程

第八篇:Java OOM 高級篇:線上Docker 上Springboot程序OOM問題的排查分享

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容