经过很多努力,我似乎无法克服获得
超出 GC 开销限制
我的 Java 程序中的错误。
它发生在一个包含大型字符串操作、许多对象列表和对 DB 的访问的大型方法中。 我尝试了以下方法:
- 使用每个 ArrayList 后,我添加了:list=new ArrayList<>(); list=null;
- 对于字符串,而不是例如 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
?实际上没有理由在每个循环结束时将cns
设置为 null。 无论如何,您在循环开始时将其设置为new Console()
- 如果可以通过将其设置为 null 来释放任何内容,那么它也将通过将其设置为新对象来释放。
您可以尝试System.gc();
调用来建议系统进行垃圾收集,但我不知道这是否会对您有所帮助或使情况变得更糟。 系统已经在尝试垃圾回收 - 如果不是,您就不会收到此错误。
您没有向我们展示您如何构建字符串,但请记住,+=
并不是唯一的罪魁祸首。 如果你有类似的东西String s = "Hello " + "to the" + " world";
,那就像把它放在三行上并使用+=
一样糟糕。 如果这是一个问题,StringBuilder
可能是你的朋友。
您可以在错误 java.lang.OutOfMemoryError: 超出 GC 开销限制中阅读答案,以获取有关如何避免此错误的其他一些建议。 对于某些人来说,它似乎是在你几乎(但不完全)失去记忆时触发的。 因此,增加 Java 可用的内存量可能会(也可能不会)有所帮助。
基本上,"超出 GC 开销限制"是可访问数据过多的症状。 堆里装满了无法垃圾收集的东西......"因为他们不是垃圾! JVM一次又一次地运行GC,试图腾出空间。 最终,它决定花太多时间收集垃圾,于是放弃了。 这通常是正确的做法。
您的问题(和其他答案)中的以下想法不是解决方案。
-
通过调用
System.gc()
强制 GC 运行无济于事。 GC 已经运行得太频繁了。 -
将
null
分配给cns
将无济于事。 它会立即获得分配给它的其他内容。 此外,没有证据表明Console
对象占用了大量内存。(请注意,
java.io.Console
类的构造函数不是public
,因此您的示例在编写时没有意义。 也许你真的在打电话给System.getConsole()
? 或者也许这是一个不同的Console
类? -
在将对象设置为
null
之前清除对象的私有属性不太可能产生任何影响。 如果对象不可访问,则其属性的值无关紧要。 GC甚至不会看他们。 -
打电话给
Thread.sleep()
不会有什么区别。 GC 在它认为需要时运行。
真正的问题是...我们无法从您提供的证据中确定的内容。 为什么有这么多可访问的数据?
一般而言,两种最可能的解释如下:
-
您的应用程序(或您的某个库)在某些数据结构中积累了越来越多的对象,这些数据结构在
for
循环的单次迭代之后幸存下来。 简而言之,您有内存泄漏。要解决此问题,您需要查找存储泄漏;请参阅如何查找 Java 内存泄漏。 (泄漏可能与数据库连接、语句或结果集未关闭有关,但我对此表示怀疑。 如果这些资源变得无法访问,GC 应查找并关闭它们。
-
您的应用程序只需要更多内存。 例如,如果对
processData
的单个调用需要的内存多于可用内存,则无论您尝试让 GC 执行什么操作,您都将获得 OOME。 它无法删除可访问的对象,并且显然无法找到足够的垃圾来足够快。要解决这个问题,首先看看是否有办法修改程序,使其需要更少(可访问)内存,这里有几个想法:
-
如果你在将输出写入
OutputStream
之前构建一个巨大的字符串来表示输出,Writer
或类似。 如果直接写入输出接收器,则会节省内存。 -
在某些情况下,在组装大字符串时,请考虑使用
StringBuilder
而不是String
串联。 特别是当串联是循环的。但是,请注意,1) 在 Java 8 及更早版本中,
javac
已经为您发出了StringBuilder
个序列用于连接表达式,以及 2) 在 Java 9+ 中,javac
发出比使用StringBuilder
更好的invokedynamic
代码;看- JDK 9/JEP 280:字符串连接永远不会相同
-
如果这没有帮助,请增加 JVM 的堆大小或将问题拆分为较小的问题。 有些问题只需要大量的内存来解决。