Java错误:java.lang.OutOfMemoryError:GC超时限制已超出

956

当我执行我的JUnit测试时,会遇到这个错误信息:

java.lang.OutOfMemoryError: GC overhead limit exceeded

我知道什么是OutOfMemoryError,但是GC overhead limit是什么意思?我该如何解决这个问题?


18
听起来很有趣。我希望有人能够发布生成这个的代码。 - Buhb
2
我简单地找到了问题,导致内存使用过多,接近堆的限制。一个简单的解决方案可能是给Java引擎一些更多的堆内存(-Xmx),但这只有在应用程序需要与之前设置的堆限制完全相同的内存才有效。 - Mnementh
16
请注意,有多种“OutOfMemoryError”情况,增加堆大小并不是有效的解决方案:耗尽本地线程和耗尽PermGen(与堆分开)是其中的两个示例。在涉及“OutOfMemoryErrors”时要小心,因为会导致它们的原因非常多样化,不要发表过于笼统的陈述。 - Tim
3
你是如何解决这个问题的? - Thorsten Niehues
2
这个错误在我使用Jdk1.8.0_91时发生并仍在发生。 - Parasu
显示剩余6条评论
23个回答

862

这条消息意味着由于某种原因,垃圾收集器花费了过多的时间(默认情况下进程 CPU 时间的 98%),并且每次运行恢复的内存很少(默认情况下堆的 2%)。

这实际上意味着您的程序停止了所有进展,并一直忙于运行垃圾收集。

为了防止应用程序在没有完成任何工作的情况下消耗 CPU 时间,JVM 抛出此 Error,以便您有机会诊断问题。

我见过这种情况发生的罕见情况是,一些代码正在创建大量临时对象和大量弱引用对象,而已经非常受限制的内存环境中。

查看 Java GC 调优指南,该指南适用于各种 Java 版本,并包含有关此特定问题的部分:

  • Java 8调优指南和其中的过度GC部分
  • Java 6调优指南和其中的过度GC部分

  • 13
    你的回答是否可以概括如下:“这就像是一个'Out of Java Heap space'错误。通过 -Xmx 增加内存即可。”? - Tim Cooper
    70
    @Tim:不,那样是不正确的。虽然增加内存可能会减少问题,但您还应该查看代码,并查看为什么它会产生那么多垃圾并且为什么您的代码接近“内存不足”标记。这通常是有问题的代码的迹象。 - Joachim Sauer
    11
    谢谢,似乎Oracle在数据迁移方面并不是很好,他们破坏了链接。 - Joachim Sauer
    5
    如果多个应用程序在同一个JVM中运行,那么它们很容易相互影响。很难确定哪个出了问题。将这些应用程序分别放到不同的JVM中可能是最简单的解决方案。 - Joachim Sauer
    4
    这种情况刚刚发生在我身上,当时我正在使用Java 7和一个包含2001670行Java代码的Web应用程序,其中我只写了大约5行。在这种情况下,“你也应该检查一下你的代码”并不那么容易。 - reinierpost
    显示剩余9条评论

    249

    引用 Oracle 文章 "Java SE 6 HotSpot[tm] 虚拟机垃圾回收调优" 中的一段:

    过度 GC 时间和 OutOfMemoryError

    如果垃圾回收花费的时间超过总时间的 98%,且恢复的堆内存少于 2%,并行收集器将抛出 OutOfMemoryError。这个特性旨在防止由于堆太小,应用程序运行了很长时间但几乎没有进展。如果需要,可以通过在命令行中添加选项 -XX:-UseGCOverheadLimit 来禁用此功能。

    编辑:看起来有人打字比我快 :)


    94
    您可以关闭这个功能,但是最好不要这样做,这可能会有不良影响。 - Stephen C
    2
    你能告诉我“-XX”和“-Xmx”的区别吗?我也能使用“-Xmx”选项关闭它。 - Susheel Javadi
    23
    回复一个非常早之前的评论,但是... @Bart 命令行选项中开头的"-XX:"是一种标志,表示这个选项高度依赖于虚拟机,而且不稳定(在将来的版本中可能会在未经通知的情况下更改)。无论如何,“-XX:-UseGCOverheadLimit”标志告诉虚拟机禁用GC超时限制检查(实际上是“关闭它”),而你的“-Xmx”命令仅仅增加了堆的大小。在后一种情况下,GC超时检查仍然运行,只是在你的情况下,似乎更大的堆解决了GC抖动问题(这并不总是有帮助的)。 - Andrzej Doyle
    1
    在我的应用程序中(在Talend读取大型Excel文件),这个方法没有起作用,听了其他用户的解释后我也明白了原因。这只是关闭了错误提示,但问题仍然存在,你的应用程序将会花费大部分时间来处理GC。我们的服务器有足够的RAM,所以我使用Vitalii的建议来增加堆大小。 - RobbZ
    在尝试以上任何操作之前,我建议关闭Android Studio并终止所有Java / JVM相关进程(或重新启动系统)。这个错误的原因之一是运行了太多的Java进程,而GC无法正常运行。现在打开您的Android Studio并尝试再次构建它,如果仍然不起作用,您可以按照之前的答案中提到的方式增加堆大小。 - Abhishek
    显示剩余3条评论

    109

    如果您确定程序中没有内存泄漏,可以尝试:

    1. 增加堆大小,例如-Xmx1g
    2. 启用并发低暂停时间垃圾收集器-XX:+UseConcMarkSweepGC
    3. 尽可能重复使用现有对象以节省一些内存。

    如有必要,可以通过将选项-XX:-UseGCOverheadLimit添加到命令行来禁用限制检查


    11
    我不同意第三条建议。重复使用现有对象并不能节省内存(但不要泄漏旧对象的话可以节省内存 :-) 此外,“重复使用现有对象”是为了减轻垃圾回收压力而采取的做法。但这并不总是一个好主意:对于现代垃圾回收,我们应该避免旧对象持有新对象的情况,因为它可能会破坏一些局部性假设... - mcoolive
    @mcoolive:为了举一个有点牵强的例子,请参见下面 https://dev59.com/anM_5IYBdhLWcg3wZSTX#5640498 的回答中的评论;在循环内部创建 List 对象导致 GC 被调用了 39 次,而不是 22 次。 - Mark Stewart
    将1GB的堆限制称为增加是很有趣的。上个十年的需求完全不同。 - undefined

    57

    通常是代码的问题。这里有一个简单的例子:

    import java.util.*;
    
    public class GarbageCollector {
    
        public static void main(String... args) {
    
            System.out.printf("Testing...%n");
            List<Double> list = new ArrayList<Double>();
            for (int outer = 0; outer < 10000; outer++) {
    
                // list = new ArrayList<Double>(10000); // BAD
                // list = new ArrayList<Double>(); // WORSE
                list.clear(); // BETTER
    
                for (int inner = 0; inner < 10000; inner++) {
                    list.add(Math.random());
                }
    
                if (outer % 1000 == 0) {
                    System.out.printf("Outer loop at %d%n", outer);
                }
    
            }
            System.out.printf("Done.%n");
        }
    }
    

    在Windows 7 32位上使用Java 1.6.0_24-b07。

    java -Xloggc:gc.log GarbageCollector
    

    然后查看gc.log

    • 使用错误方法触发了444次
    • 使用更差的方法触发了666次
    • 使用更好的方法触发了354次

    当面临只能实现这样的循环或处理现有行为不佳的代码时,尽管这不是最好的测试或最好的设计,但选择重复使用对象而不是创建新对象可以减少垃圾回收器干扰的次数...


    14
    请澄清:当您说“触发n次”时,这是指定期GC发生了n次,还是由OP报告的“超过GC开销限制”错误发生了n次? - Jon Schneider
    我刚刚使用Java 1.8.0_91进行了测试,从未出现过错误/异常,并且“触发n次”是通过计算gc.log文件中行数的数量得出的。我的测试显示总体上次数要少得多,但BETTER的“触发”次数最少,而现在BAD比WORST更糟糕。我的计数:BAD:26,WORSE:22,BETTER:21。 - Mark Stewart
    我刚刚添加了一个“WORST_YET”修改,其中我在外部循环中定义了List<Double> list,而不是在外部循环之前,并触发了39次垃圾回收。 - Mark Stewart

    39
    根据Java [8] 平台标准版故障排除指南,造成错误的原因如下(已强调):
    [...] "GC overhead limit exceeded" 表示垃圾回收器一直在运行,而 Java 程序进展非常缓慢。 在垃圾收集之后,如果 Java 进程花费的时间做垃圾收集超过了大约 98%,并且如果它恢复不到2%的堆大小,并已经这样做了最近5次(编译时常数)连续的垃圾收集,则会抛出java.lang.OutOfMemoryError异常。 [...]
    以下是解决方法:
    1. 增加堆大小,如果当前堆大小不够。
    2. 如果增加堆内存后仍然出现此错误,请使用内存分析工具,例如MAT(内存分析工具),Visual VM等,修复内存泄漏问题。
    3. 升级JDK版本至最新版本(1.8.x)或至少1.7.x,并使用G1GC算法。 G1 GC的吞吐量目标是90%的应用程序时间和10%的垃圾收集时间
    4. 除了使用 -Xms1g -Xmx2g 设置堆内存外,也可以尝试

      -XX:+UseG1GC -XX:G1HeapRegionSize=n -XX:MaxGCPauseMillis=m  
      -XX:ParallelGCThreads=n -XX:ConcGCThreads=n
      

    查看更多有关G1GC的相关问题


    32

    只需在运行配置的VM参数中设置该选项,稍微增加堆大小即可。

    路径为:运行 → 运行配置 → 参数 → VM参数

    -Xms1024M -Xmx2048M
    

    Xms - 用于指定最小限制

    Xmx - 用于指定最大限制


    2
    Android应用程序没有“参数”选项卡...我们该怎么做才能实现这个功能呢? - Blaze Tama
    3
    这个答案适用于什么工具?那不是一个关于Eclipse的问题。 - Michael Piefel
    3
    没有“最小限制”。-Xms是初始大小。 - Diego Queiroz
    1
    最大可设置的最大限制是多少? - JPerk
    @JPerk,最大值取决于您计算机的物理内存。但是,如果您尝试这样做,其他应用程序将竞争使用内存。 - PJvG

    16

    试一试

    打开build.gradle文件

      android {
            dexOptions {
               javaMaxHeapSize = "4g"
            }
       }
    

    在模拟器上运行得很好。有没有想过这对真实设备有什么影响?也就是说,这是个好主意还是只是掩盖了问题?谢谢。 - Joshua Pinter

    13

    以下是对我有效的步骤:

    1. 打开 eclipse.ini 文件
    2. 更改

      -Xms40m
      -Xmx512m
      

      -Xms512m
      -Xmx1024m
      
    3. 重新启动 Eclipse

    点击此处


    最简单的解决方法。谢谢 :) - Hamza
    2
    在jdev中的eclipse.ini文件? - Abhinaba Basu
    问题仍未解决,即使配置已更改为此。 - zionpi
    问题尚未解决。你有/知道其他的方法吗? - Phoenix
    2
    OP没有提出Eclipse相关的问题。 - Michael Piefel
    2
    这个“答案”并没有回答上面的问题。 - Freitags

    13

    以下方法适用于我。只需添加以下片段:

    android {
            compileSdkVersion 25
            buildToolsVersion '25.0.1'
    
    defaultConfig {
            applicationId "yourpackage"
            minSdkVersion 10
            targetSdkVersion 25
            versionCode 1
            versionName "1.0"
            multiDexEnabled true
        }
    dexOptions {
            javaMaxHeapSize "4g"
        }
    }
    

    是的,当使用Gradle时 :) - Alex
    4
    你怎么会认为这是一般情况下对他问题的解决办法呢?在 Android 的 Gradle 配置中将堆大小设置为 4g,这完全是随意的做法,摊手 - Julian L.

    9
    在您的build.gradle(Module:app)文件中增加javaMaxHeapsize。
    dexOptions {
        javaMaxHeapSize "1g"
    }
    

    添加以下行到gradle中:

     dexOptions {
            javaMaxHeapSize "4g"
        }
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接