我在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是这种情况发生了,我怎么能“打击”这个(不正确的?)行为?
答案 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
,则可以使用Guava的FluentIterable.transformAndConcat()
将基础对象的集合转换为懒惰 - 评估了他们的时间序列的Iterable
串联。当然,集合的大小不再可以直接使用(如果你试图独立于迭代,你将评估延迟集合两次)。