避免“超出 GC 开销限制”错误

Posted

技术标签:

【中文标题】避免“超出 GC 开销限制”错误【英文标题】:Avoid "GC overhead limit exceeded" error 【发布时间】:2018-04-10 05:22:56 【问题描述】:

经过很多努力,我似乎​​无法克服得到一个的问题

超出 GC 开销限制

我的 Java 程序出错。

它发生在一个大型方法中,该方法包含大型字符串操作、许多对象列表和对 DB 的访问。 我尝试了以下方法:

    在每个ArrayList使用后,我都添加了:list=new ArrayList();列表=空; 用于字符串,而不是例如50 个附加 (str+="....") 我尝试在总文本中附加一个 每次访问数据库后,我都会关闭语句和结果集。

这个方法是这样从main调用的:

for(int i=0; i<L; i++) 
    cns = new Console(i);

    cns.processData();//this is the method

    cns=null;

当此循环执行 1 或 2 次时,一切正常。对于 L>=3,几乎可以肯定我会收到垃圾收集器错误。

在每次执行该方法后我有一个 cns=null 的事实不应该强制 GC 并从之前的执行中释放所有内容吗?

在将对象设置为 null 之前,我还应该删除对象的所有私有属性吗?也许放置一个 Thread.sleep() 可以在每个循环之后强制 GC?

【问题讨论】:

可能你第三次运行占用了太多内存。我建议将您的大方法拆分为较小的方法。手动调用 GC 和 Thread.spleep 是错误的做法。 执行list=new ArrayList&lt;&gt;(); list=null; 只会使必须进行垃圾收集的列表数量增加一倍。您正在创建一个新的 ArrayList,然后立即将其设置为 null。 如果您有很多附加字符串,请使用 StringBuilder。 我只遇到了一个问题,即手动调用垃圾收集很有用,但这是由于速度而不是空间不足。如果不手动调用 gc,gui 中会出现奇怪的停顿。事件循环中的 System.gc() 解决了这个问题。 【参考方案1】:

实际上没有理由在每个循环结束时将 cns 设置为 null。无论如何,您在循环开始时将其设置为 new Console() - 如果可以通过将其设置为 null 来释放任何内容,那么也可以通过将其设置为新对象来释放它。

您可以尝试拨打System.gc(); 建议系统进行垃圾回收,但我不知道这是否会帮助您或使情况变得更糟。系统已在尝试垃圾收集 - 如果没有,您将不会收到此错误。

您没有向我们展示您是如何构建字符串的,但请记住,+= 并不是唯一的罪魁祸首。如果你有类似String s = "Hello " + "to the" + " world"; 的东西,那就像把它放在三行并使用+= 一样糟糕。如果这是一个问题,StringBuilder 可能是你的朋友。

您可以阅读Error java.lang.OutOfMemoryError: GC overhead limit exceeded 的答案,了解有关如何避免此错误的其他建议。似乎对于某些人来说,当您几乎(但不是完全)内存不足时会触发它。因此,增加可供 Java 使用的内存量可能会(也可能不会)有所帮助。

【讨论】:

感谢您的回答。 StringBuilder 提示帮助了我。此外,在数据库方面。除了在每条语句之后关闭语句和 ResultSet 之外,偶尔关闭连接是否也是一种好习惯?这会节省内存吗? 它可能会节省少量内存,但除非您在数据库调用之间进行大量处理或等待用户输入,否则可能不值得。如果你不断关闭连接并建立新连接,那只会意味着更多的垃圾收集。 使用String s = "Hello " + "to the" + " world"; 很好。这些都是常量,编译器会对其进行优化。只有当有变量(可能来自数据库)时才会出现问题。【参考方案2】:

基本上,“超出 GC 开销限制”是可访问数据过多的症状。堆里装满了不能被垃圾收集的东西……因为它们不是垃圾! JVM 一次又一次地运行 GC 以试图腾出空间。最终,它决定在垃圾收集上花费了太多时间,于是放弃了。这通常是正确的做法。


您的问题(和其他答案)中的以下想法不是灵魂。

    通过调用 System.gc() 强制 GC 运行不会有帮助。 GC 已经运行过于频繁

    null 分配给cns 无济于事。它立即得到分配给它的其他东西。此外,没有证据表明Console 对象占用了大量内存。

    (请注意java.io.Console 类的构造函数不是public,因此您的示例在编写时没有意义。也许您实际上是在调用System.getConsole()?或者这是一个不同的Console 类?)

    在将对象设置为null 之前清除其私有属性不太可能产生任何影响。如果一个对象不可达,那么它的属性值是不相关的。 GC 甚至不会看它们。

    致电Thread.sleep() 不会有任何影响。 GC 在它认为需要时运行。


真正的问题是......你没有解释的东西。

两种最可能的解释(IMO)如下:

您的应用程序(或您所在的某个库)在某个数据结构中累积越来越多的对象,这些对象在for 循环的单次迭代之后仍然存在。简而言之,你有内存泄漏。

您的应用程序只需要更多内存。例如,如果对processData 的单个调用需要比可用内存更多的内存,那么无论您尝试让 GC 执行什么操作,您都会得到 OOME。它无法删除可达对象,而且它显然无法以足够快的速度找到足够多的垃圾。

这些问题的解决方案是不同的。

对于第一个,你需要找到存储泄漏;见How to find a Java Memory Leak。 (泄漏可能与数据库连接、语句或结果集未关闭有关,但我对此表示怀疑。GC 应该找到并收集这些资源......如果它们变得无法访问。)

对于第二个,您需要修改程序以使其需要更少(可达)内存,或者您需要增加 JVM 的堆大小。

对于前者,一种可能性是在将输出写入OutputStreamWriter 或类似名称之前,您正在构建一个巨大的字符串来表示输出。如果你直接写到输出接收器,你会节省内存。

【讨论】:

以上是关于避免“超出 GC 开销限制”错误的主要内容,如果未能解决你的问题,请参考以下文章

跟踪“超出 GC 开销限制”错误

布局 xml 上的 Java 堆空间错误:超出 GC 开销限制

在 Android Studio 中启用 R8 Shrinker 时超出 GC 开销限制

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

如何使用 maven jvmArg 解决“超出 GC 开销限制”?

将大量数据写入 excel:超出 GC 开销限制