老年代堆空间溢出

3

我在Java中遇到了一个非常奇怪的GC问题。我正在运行以下代码:

 while(some condition){
        //do a lot of work...
        logger.info("Generating resulting time series...");
        Collection<MetricTimeSeries> allSeries = manager.getTimeSeries();
        logger.info(String.format("Generated %,d time series! Storing in files now...", allSeries.size()));

        //for (MetricTimeSeries series : allSeries) {
           // just empty loop
        //}
 }

当我查看JConsole时,在每次循环迭代重新启动时,如果我手动强制GC,我的旧生代堆空间占用约90 MB的大小。如果我取消循环的注释,就像这样:

 while(some condition){
        //do a lot of work...
        logger.info("Generating resulting time series...");
        Collection<MetricTimeSeries> allSeries = manager.getTimeSeries();
        logger.info(String.format("Generated %,d time series! Storing in files now...", allSeries.size()));

        for (MetricTimeSeries series : allSeries) {
           // just empty loop
        }
 }

即使我强制刷新,它也不会低于550MB。根据您的YourKit分析器,TimeSeries对象可以通过主线程的本地变量(集合)访问,在新迭代重新启动后进行GC之后...而且集合非常庞大(250K时间序列)。为什么会发生这种情况,如何“对抗”这种(错误的?)行为?

manager.getTimeSeries(); 做什么?此外,它返回的值迭代器函数是做什么的? - Benjamin Gruenbaum
它正在构建一个包含其所知的所有时间序列的ArrayList(底层对象反过来也是如此,对于它们管理的所有时间序列)。最后,构建一个大的ArrayList并返回,迭代器只是List的标准Java迭代器。 - Bober02
它是否会在某个条件下退出 while 循环? 顺便说一句,试图解决一个黑盒问题真的很困难。 - bluesman
无论最终结果如何,如果我将allSeries设置为null,或者干脆不进行迭代,堆空间都会降至正确的值... 真是令人困惑? - Bober02
2个回答

2
是的,垃圾回收器可能会很神秘,但它比自己管理内存要好。集合和映射有一种让引用保持时间比你预期的时间长的方式,从而防止垃圾回收。正如您注意到的那样,将“allSeries”引用设置为“null”本身将使其标记为垃圾回收,因此其内容也可以被回收。另一种方法是调用“allSeries.clear()”,这将取消所有的“MetricTimeSeries”对象链接,它们将可以自由地进行垃圾回收。
为什么删除循环也能解决这个问题呢?这是更有趣的问题。我倾向于认为编译器正在优化对“allSeries”的引用,但您仍然调用了“allSeries.size()”,所以它不能完全优化掉引用。
为了使事情更加混乱,不同的编译器(和设置)行为不同,并使用不同的垃圾回收器,它们本身的行为也不同。没有更多的信息很难准确地说出底层发生了什么。

将集合传递给其他方法进行处理也可以解决问题...什么鬼??? - Bober02
顺便提一下,替换常规迭代器并调用iterator.remove不会改变任何内容。 - Bober02

1

由于您正在构建一个(大型)ArrayList时间序列,只要它被引用,它就会占用堆,并且如果它停留足够长时间(或者年轻代太小而无法实际容纳它),它将被提升为旧的。我不确定您如何将您在JConsole或Yourkit中看到的信息与程序中的特定点相关联,但在空循环被多个JIT传递优化之前,您的while循环将需要更长时间并保持集合更长时间,这可能解释了感知差异,而实际上并没有太多。

这种行为没有任何错误。如果您不想消耗太多内存,您需要更改Collection,使其不是急切填充的ArrayList,而是一种懒惰的集合,更像是流(如果您曾经进行过XML处理,请考虑DOM vs SAX),当迭代时进行评估。如果您不需要整个集合排序,那是可行的,特别是因为您似乎在说该集合是由底层对象返回的子集合的串联。

如果您可以将返回类型从Collection更改为Iterable,则可以例如使用GuavaFluentIterable.transformAndConcat()将基础对象的集合转换为惰性评估的Iterable时间序列连接。当然,集合的大小不再直接可用(如果您尝试独立于迭代获取它,则会两次评估惰性集合)。


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