常見(jiàn)OOM問(wèn)題之GC overhead limit exceeded 問(wèn)題詳解

本文來(lái)自于HeapDump性能社區(qū)! !有性能問(wèn)題,上HeapDump性能社區(qū)!

正文

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

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

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

1,是什么原因造成的?

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

1.png

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

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

2,舉個(gè)例子

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

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

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

java -Xmx100m -XX:+UseParallelGC Wrapper

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

java -Xmx10m -XX:+UseParallelGC Wrapper

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

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

3,解決辦法是什么?

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

-XX:-UseGCOverheadLimit

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

更嚴(yán)重的一點(diǎn)是 - 有時(shí)會(huì)觸發(fā) GC 開(kāi)銷限制錯(cuò)誤,因?yàn)槟峙浣o JVM 的堆數(shù)量不足以滿足在該 JVM 上運(yùn)行的應(yīng)用程序的需求。在這種情況下,你應(yīng)該只分配更多的堆——請(qǐng)參閱本章末尾以了解如何實(shí)現(xiàn)這一點(diǎn)。

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

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

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

此時(shí),請(qǐng)確保在您的日歷中清除幾天(或 – 請(qǐng)參閱項(xiàng)目符號(hào)列表下方的自動(dòng)方式)。以下是一個(gè)粗略的流程大綱,可以幫助您回答上述問(wèn)題:

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

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

2.png

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

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

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

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

java -Xmx1024m com.yourcompany.YourClass

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

Java OOM系列專題:

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

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

第三篇:Java OOM 基礎(chǔ)篇:常見(jiàn)的OutOfMemoryError 場(chǎng)景二 : GC overhead limit exceeded 問(wèn)題詳解

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

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

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

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

第八篇:Java OOM 高級(jí)篇:線上Docker 上Springboot程序OOM問(wèn)題的排查分享

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容