译者注
原文: https://www.baeldung.com/java-gc-overhead-limit-exceeded
Demo: https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-perf
1. 概述
简单来说,当应用中的对象不再被使用时,JVM便会进行回收内存的操作,我们把这个操作称为垃圾回收,对应的原文为Garbage Collection
,简称GC
。
GC Overhead Limit Exceeded error
提示是由java.lang.OutOfMemoryError
发出的,此异常代表资源已经被耗尽。
本文中我们将介绍引发java.lang.OutOfMemoryError: GC Overhead Limit Exceeded error
的原因及解决方案。
2. “垃圾回收超出开销限制”的原因(GC Overhead Limit Exceeded Error)
OutOfMemoryError
类是java.lang.VirtualMachineError
的一个子类,当JVM 遇到资源相关的问题时,就会抛出此VirtualMachineError
异常。
具体来讲,当JVM花费了太多的时间去执行垃圾回收,却只回收了很少的可用堆空间时,这个错误就会出现。
根据Java的文档, 在默认情况下JVM的设计是:如果Java进程花费了超过98%的时间去执行垃圾回收,而每次只收回了不到2%的内存时,就会抛出这个错误。
换句话讲,这就意味着我们的程序耗尽了几乎所有的可用内存,并且GC已经花费了太多了时间去清理内存,却不断的失败,不断的重复。
在这种情况下,这个应用程序的用户体验会变得非常缓慢。那些本来只需要花费毫秒级时间就能完成的操作,现在却要花费更多的时间才能完成。这是因为CPU正在使用它的计算资源来执行GC操作,因此无法执行任何其他的任务。
3. 复现这个问题
我们看一下抛出java.lang.OutOfMemoryError: GC Overhead Limit Exceeded
错误的代码。
我们可以知道,例如,通过添加键值对在一个不会结束的循环(死循环)中:
public class OutOfMemoryGCLimitExceed {
public static void addRandomDataToMap() {
Map dataMap = new HashMap<>();
Random r = new Random();
while (true) {
dataMap.put(r.nextInt(), String.valueOf(r.nextInt()));
}
}
}
当这个方法被调用的时候,带着如下的JVM参数运行`-Xmx100m -XX:+UseParallelGC` (这个参数的含义是:设置Java的堆大小为100M,GC收集算法为并行收集),我们就会得到一个`java.lang.OutOfMemoryError: GC Overhead Limit Exceeded`错误。
如果想要对于不同的垃圾回收策略有更深入的理解,可以学习Oracle的文章:[ Java Garbage Collection Basics tutorial.](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html " Java Garbage Collection Basics tutorial")
通过在项目的根目录运行下面的命令,我们马上就可以得到一个`java.lang.OutOfMemoryError: GC Overhead Limit Exceeded`错误:
mvn exec:exec
此外,值得注意的是,有些情况下,我们在遇到`GC Overhead Limit Exceeded`错误之前,先遇到了一个堆空间错误(`heap space error`)。
4. 解决“GC超出开销限制”错误
理想的办法就是,通过检查代码中的内存泄漏,去查找软件中的潜在问题、
下面的问题值得被解决:
- 软件中的哪些对象占用了堆中的大量内存?
- 这些对象在代码中的哪个位置被创建
我们也可以使用自动化图形工具(例如[JConsole](https://docs.oracle.com/en/java/javase/11/management/using-jconsole.html#GUID-77416B38-7F15-4E35-B3D1-34BFD88350B5 "JConsole")),来帮助我们检测代码中的性能问题,包括文中一直提到的java.lang.OutOfMemoryErrors。
最后一个办法是,通过改变JVM启动时的配置来增加堆内存。例如,给我们运行的Java程序设置1GB的堆内存:
java -Xmx1024m com.xyz.TheClassName
然而,这种做法并不能解决应用程序中存在的内存泄漏问题,而是会推迟这个问题的发生。因此,更可取的办法是,彻底重新评估应用程序的内存使用情况。
5. 结论
在本文中,我们学习了 java.lang.OutOfMemoryError: GC Overhead Limit Exceeded
错误,并且讨论了错误出现的原因以及如何解决它。
和其他文章一样,读者可以在Github中找到示例代码。