Java运行时环境包含内置垃圾收集(GC)过程。在许多其他编程语言中,开发人员需要手动分配和释放内存区域,以便可以重用已释放的内存。
另一方面,Java应用程序只需要分配内存。每当不再使用内存中的特定空间时,一个名为Garbage Collection的独立进程会清除它们的内存。GC如何检测垃圾收集手册中更详细地解释了特定部分内存,但您可以信任GC以完成其工作。
该java.lang.OutOfMemoryError:GC开销超过限制时显示错误您的应用程序已经耗尽了几乎所有的可用内存和GC一再未能清除它。
该java.lang.OutOfMemoryError:GC开销超过极限误差信号,你的应用程序花费太多的时间做垃圾收集太少的结果JVM的方式。默认情况下,如果JVM花费超过总时间的98%来执行GC,并且在GC之后只有不到2%的堆被恢复,则JVM被配置为抛出此错误。
如果此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堆空间的基本问题而不是屏蔽症状,则需要确定代码的哪一部分负责分配最多的内存。换句话说,您需要回答以下问题:
此时,请务必在日历中清除几天(或者 - 在项目符号列表下方以自动方式查看)。这是一个粗略的流程大纲,可以帮助您回答上述问题:
或者,我们建议使用Plumbr,这是唯一具有自动根本原因检测功能的Java监控解决方案。在其他性能问题中,它捕获所有java.lang.OutOfMemoryError并自动向您提供有关最需要内存的数据结构的信息。它负责在幕后收集必要的数据 - 这包括有关堆使用的相关数据(只有对象布局图,没有实际数据),还有一些你甚至无法在堆转储中找到的数据。它还会在JVM遇到java.lang.OutOfMemoryError时立即为您进行必要的数据处理。以下是来自Plumbr的java.lang.OutOfMemoryError事件警报示例:
无需任何额外的工具或分析,您可以看到:
配备此信息,您可以放大根本原因,并确保将数据结构调整到适合您的内存池的级别。
但是,当您从内存分析或阅读Plumbr报告得出的结论是内存使用是合法的并且源代码中没有任何变化时,您需要允许JVM更多Java堆空间正常运行。在这种情况下,更改您的JVM启动配置并在启动脚本中添加(或增加值,如果存在)一个参数:
java -Xmx1024m com.yourcompany.YourClass
在上面的示例中,Java进程被赋予1GB的堆。将值修改为最适合您的JVM。但是,如果结果是您的JVM仍然因OutOfMemoryError而死亡,您可能仍然无法避免上述手动或Plumbr辅助分析。