老gen堆空间溢出

时间:2012-12-15 14:39:22

标签: java garbage-collection

我在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 profiler,TimeSeries对象可以通过主线程的本地var(集合)访问,就在GC重新启动新迭代之后...而且集合很大(250K时间序列。)... Wyy是这种情况发生了,我怎么能“打击”这个(不正确的?)行为?

2 个答案:

答案 0 :(得分:2)

是的,垃圾收集器可能很神秘......但是它可以管理你自己的记忆;)

集合和地图有一种比可能更长时间挂在引用上的方式,因此可以防止垃圾收集。正如您所注意到的,将allSeries引用设置为null本身会将其标记为垃圾收集,因此它的内容也可用于抓取。另一种方法是调用allSeries.clear():这将取消所有它的MetricTimeSeries对象的链接,它们将免费用于垃圾收集。

为什么删除循环也会解决这个问题呢?这是一个更有趣的问题。我很想建议编译器优化对allSeries的引用..但是你仍然在调用allSeries.size(),所以它不能完全优化引用。

为了使水变得混乱,不同的编译(和设置)表现不同,并使用不同的垃圾收集器,它们本身的行为也不同。如果没有更多信息,很难确切地说出幕后发生了什么。

答案 1 :(得分:1)

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

这种行为没有任何不正确之处。如果你不想消耗这么多内存,你需要更改你的Collection所以它不是一个充满热切的ArrayList,而是一个懒惰的集合,更多的是一个流(如果你曾经有过)完成XML处理,思考DOM vs SAX),它在迭代时得到评估。如果你不需要对整个集合进行排序,这是可行的,特别是因为你似乎在说集合是由底层对象返回的子集合的串联。

如果您可以将返回类型从Collection更改为Iterable,则可以使用GuavaFluentIterable.transformAndConcat()将基础对象的集合转换为懒惰 - 评估了他们的时间序列的Iterable串联。当然,集合的大小不再可以直接使用(如果你试图独立于迭代,你将评估延迟集合两次)。