避免“超出 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<>(); 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 的堆大小。
对于前者,一种可能性是在将输出写入OutputStream
、Writer
或类似名称之前,您正在构建一个巨大的字符串来表示输出。如果你直接写到输出接收器,你会节省内存。
【讨论】:
以上是关于避免“超出 GC 开销限制”错误的主要内容,如果未能解决你的问题,请参考以下文章
布局 xml 上的 Java 堆空间错误:超出 GC 开销限制
在 Android Studio 中启用 R8 Shrinker 时超出 GC 开销限制
引起:java.lang.OutOfMemoryError:超出GC开销限制