GC overhead limit exceeded] with root cause java.lang.OutOfMemoryError: GC overhead limit exceeded

java.lang.OutOfMemoryError:
超出了GC开销限制

Java运行时环境包含内置垃圾收集(GC)过程。在许多其他编程语言中,开发人员需要手动分配和释放内存区域,以便可以重用已释放的内存。

另一方面,Java应用程序只需要分配内存。每当不再使用内存中的特定空间时,一个名为Garbage Collection的独立进程会清除它们的内存。GC如何检测垃圾收集手册中更详细地解释了特定部分内存,但您可以信任GC以完成其工作。

java.lang.OutOfMemoryError:GC开销超过限制时显示错误您的应用程序已经耗尽了几乎所有的可用内存和GC一再未能清除它

是什么造成的?

java.lang.OutOfMemoryError:GC开销超过极限误差信号,你的应用程序花费太多的时间做垃圾收集太少的结果JVM的方式。默认情况下,如果JVM花费超过总时间的98%来执行GC,并且在GC之后只有不到2%的堆被恢复,则JVM被配置为抛出此错误。

GC overhead limit exceeded] with root cause java.lang.OutOfMemoryError: GC overhead limit exceeded_第1张图片

如果此GC开销限制不存在会发生什么?请注意,仅在几个GC周期后释放2%的内存时才会抛出java.lang.OutOfMemoryError:GC开销限制超出错误。这意味着GC能够清洁的少量堆可能会再次快速填充,迫使GC再次重新启动清洗过程。这形成了一个恶性循环,CPU在100%忙于GC并且无法进行实际工作。应用程序的最终用户面临极端减速 - 通常在几毫秒内完成的操作需要几分钟才能完成。

因此,“ java.lang.OutOfMemoryError:GC开销限制已超出 ”消息是一个非常好的示例,其中包含一个失败快速原则。

立即安装Plumbr以开始监控您的应用程序。尝试PLUMBR

举个例子

在下面的示例中,我们通过初始化Map并在未终止的循环中将键值对添加到地图中来创建“ 超出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");
    }
  }
}

你可能会猜到这不会很好。事实上,当我们启动上述程序时:

java -Xmx100m -XX:+UseParallelGC Wrapper

我们很快就会遇到java.lang.OutOfMemoryError:超出GC开销限制的消息。但上面的例子很棘手。当使用不同的Java堆大小或不同的GC算法启动时,带有Hotspot 1.7.0_45的Mac OS X 10.9.2将选择不同的方式。例如,当我运行具有较小Java堆大小的程序时,如下所示:

java -Xmx10m -XX:+UseParallelGC Wrapper

应用程序将死于更常见的java.lang.OutOfMemoryError:在Map resize上抛出的Java堆空间消息。当我使用除ParallelGC之外的其他垃圾收集算法运行它时,例如-XX:+ UseConcMarkSweepGC或-XX:+ UseG1GC,错误由默认异常处理程序捕获,并且没有堆栈跟踪,因为堆耗尽到了在创建异常时甚至无法填充堆栈跟踪。

这些变化是真正的好例子,它们证明在资源有限的情况下,您无法预测应用程序将要死的方式,因此不要将您的期望建立在要完成的特定操作序列上。

解决办法是什么?

作为一个诙谐的解决方案,如果您只是希望摆脱“ java.lang.OutOfMemoryError:GC开销超出限制 ”消息,将以下内容添加到您的启动脚本中就可以实现:

-XX:-UseGCOverheadLimit

强烈建议不要使用这个选项 - 而不是解决问题,你只是推迟不可避免的:应用程序耗尽内存并需要修复。指定此选项将仅使用更熟悉的消息java.lang.OutOfMemoryError:Java堆空间来掩盖原始java.lang.OutOfMemoryError:GC开销限制超出错误。

更严重的是 - 有时会触发GC开销限制错误,因为您分配给JVM的堆数量不足以满足在该JVM上运行的应用程序的需求。在这种情况下,您应该只分配更多堆 - 请参阅本章末尾有关如何实现这一点。

但是,在许多情况下,提供更多的Java堆空间并不能解决问题。例如,如果您的应用程序包含内存泄漏,则添加更多堆只会推迟java.lang.OutOfMemoryError:Java堆空间错误。此外,增加Java堆空间量也会增加GC暂停的长度,从而影响应用程序的吞吐量或延迟。

如果您希望解决Java堆空间的基本问题而不是屏蔽症状,则需要确定代码的哪一部分负责分配最多的内存。换句话说,您需要回答以下问题:

  1. 哪些对象占据堆的大部分
  2. 这些对象在源代码中分配的位置

此时,请务必在日历中清除几天(或者 - 在项目符号列表下方以自动方式查看)。这是一个粗略的流程大纲,可以帮助您回答上述问题:

  • 获得从JVM到故障排除获取堆转储的许可。“转储”基本上是您可以分析的堆内容的快照,并包含应用程序在转储时保留在内存中的所有内容。包括密码,信用卡号等。
  • 指示您的JVM将其堆内存的内容转储到文件中。准备好获得一些转储,因为在错误的时间采取,堆转储包含大量的噪音,实际上可能没用。另一方面,每个堆转储都会完全“冻结”JVM,因此请不要占用太多,或者最终用户开始咒骂。
  • 找到可以加载转储的机器。当您的JVM-to-troubleshoot使用例如8GB的堆时,您需要一台超过8GB的计算机才能分析堆内容。启动转储分析软件(我们推荐Eclipse MAT,但也有同样好的替代方案)。
  • 检测堆的最大消费者的GC根路径。我们在此处单独发布了此活动。别担心,一开始会觉得很麻烦,但是经过几天挖掘后你会变得更好。
  • 接下来,您需要确定源代码中的哪些位置正在分配潜在危险的大量对象。如果您对应用程序的源代码有很好的了解,那么您希望能够在几次搜索中完成此操作。如果运气不佳,你需要一些能量饮料才能提供帮助。

或者,我们建议使用Plumbr,这是唯一具有自动根本原因检测功能的Java监控解决方案。在其他性能问题中,它捕获所有java.lang.OutOfMemoryError并自动向您提供有关最需要内存的数据结构的信息。它负责在幕后收集必要的数据 - 这包括有关堆使用的相关数据(只有对象布局图,没有实际数据),还有一些你甚至无法在堆转储中找到的数据。它还会在JVM遇到java.lang.OutOfMemoryError时立即为您进行必要的数据处理。以下是来自Plumbr的java.lang.OutOfMemoryError事件警报示例:

无需任何额外的工具或分析,您可以看到:

  • 哪些对象占用的内存最多(271 com.example.map.impl.PartitionContainer实例占用总容量为248MB的173MB)
  • 分配这些对象的位置(大多数在MetricManagerImpl类中分配,第304行)
  • 什么是当前引用这些对象(完整的参考链到GC根目录)

配备此信息,您可以放大根本原因,并确保将数据结构调整到适合您的内存池的级别。

但是,当您从内存分析或阅读Plumbr报告得出的结论是内存使用是合法的并且源代码中没有任何变化时,您需要允许JVM更多Java堆空间正常运行。在这种情况下,更改您的JVM启动配置并在启动脚本中添加(或增加值,如果存在)一个参数:

java -Xmx1024m com.yourcompany.YourClass

在上面的示例中,Java进程被赋予1GB的堆。将值修改为最适合您的JVM。但是,如果结果是您的JVM仍然因OutOfMemoryError而死亡,您可能仍然无法避免上述手动或Plumbr辅助分析。

你可能感兴趣的:(GC overhead limit exceeded] with root cause java.lang.OutOfMemoryError: GC overhead limit exceeded)