有没有一种方法在Java中启动积极且完整的垃圾回收?

6
为了内存优化的原因,在进行性能分析时,我会启动垃圾回收器来检查对象在释放后是否被正确清理。
然而,仅仅调用垃圾回收器是不够的,而且似乎也不能保证它将清理什么内容。
在性能分析条件下,有没有一种方法可以调用它,以确保它能恢复尽可能多的内容(当然,在生产环境中这是没有意义的)?或者说,“多次调用”是唯一“几乎确定”的方法吗?
还是我对垃圾回收器有所误解?

你正在使用哪个剖析器? - matt b
@matt - 我想这适用于所有的工具,但你可以试试YourKit。 - Gnoupi
你如何真正强制进行垃圾回收:https://dev59.com/sXI95IYBdhLWcg3wvgh9 - msw
1
@msw:那个话题没有新增任何内容。 - BalusC
4个回答

4
总的来说,“完整的垃圾回收”没有一个明确定义。垃圾回收会检测不可达对象并将其回收。大多数垃圾回收实现都是基于“循环”运行的,一旦完整的循环运行完成,就可以定义一个“回收空间”的合理概念。因此,如果您可以运行一个完整的循环,并且该循环根本没有找到任何可回收的空间,并且应用程序在其他方面处于“空闲”状态(例如,不更新指针),那么您可以说您已经暂时达到了“完全收集”的点。
因此,您可能想要做这样的事情:
Runtime r = Runtime.getRuntime();
r.gc();
long f = r.freeMemory();
long m = r.maxMemory();
long t = r.totalMemory();
for (;;) {
    r.gc();
    long f2 = r.freeMemory();
    long m2 = r.maxMemory();
    long t2 = r.totalMemory();
    if (f == f2 && m == m2 && t == t2) 
        break;
    f = f2; 
    m = m2; 
    t = t2; 
}   
System.out.println("Full GC achieved.");

这段代码依赖于一些假设,其中最重要的是Runtime.gc()不仅仅是一个提示,而是真正强制进行一些GC活动。Sun的JVM可以使用"-XX:-DisableExplicitGC"标志启动,这将把Runtime.gc()转换为无操作,有效地防止上面的代码执行任何操作。
还有一些注意事项:
  • JVM可能会有一些后台连续活动,例如Timer线程。其中一些活动可能是“隐藏”的,即不是应用程序开发人员编写的代码。此外,请考虑AWT / Swing事件分派器线程。

  • GC运行可能会通过可终结对象或软/弱/幻象引用触发额外的异步活动。这种活动必须是异步的,并且可能运行不受限制的时间。 Runtime.gc()不会等待其完成。实际上,这是一个定义问题:如果尚未处理完成器和引用队列,则您是否认为GC已经“完成”?

  • 没有什么能够强制上述代码终止...

为了进行“更完整”的GC,您可以使用JVMTI接口(调试器使用的接口),它允许您运行GC,但可以暂停目标JVM线程,这样更容易定义和实现“完整GC”状态。

是的,我显然忘记了GC的操作方式。 - Gnoupi

4

强制分配最大可用内存。当JVM处于OutOfMemoryError的边缘时,它会被迫运行GC。

byte[] boom = new byte[512 * 1024 * 1024]; // Assuming 512MB

显然,这只是为了纯粹的测试目的 :)

另一个选择是使用更好的分析器。它可以提供有关内存使用情况和GC可选对象的更好报告。


我直接从分析器中启动GC,不需要从代码中触发它。无论如何,谢谢。 - Gnoupi
如果从分析器中启动GC没有产生预期结果,那么你的分析器显然与Runtime#gc()完全相同。也就是说,是否实际运行GC取决于JVM。当你强制将内存分配到最大以导致(接近)OOME时,JVM 执行GC。要确认这一点,跟踪Object#destroy() - BalusC
事实是,从分析器中调用GC确实会显示其使用情况(在适当的选项卡中),以及内存增益。因此理论上来说,一切都正常。但在释放后再次调用它可以释放更多的内存,这让我认为第一次调用不够。 - Gnoupi
1
啊,原来是这样。JVM是否运行彻底的GC也取决于它自己。有时它只会清理“最老”的未引用对象,或者占用最多内存的对象,甚至什么都不清理。你不知道。由于GC是一项相当昂贵的任务,所以这取决于JVM的决定。我认为你需要一个更好的分析器,这样你就不需要运行GC了。 - BalusC
我正在使用YourKit,到目前为止,我发现它是一个相当不错的分析器。你有更好的建议吗?或者说我还没有发现它的某些功能? - Gnoupi

2

不,你不能确定性地启动完整的垃圾回收。

如果你想确保在你的分析尝试期间GC已经运行过,以删除通常会被删除的任何对象(以便找到实际泄漏的位置),那么最好确保你的测试运行足够长的时间,可以合理地预期GC会运行(或会运行多次)。


我自己启动了垃圾回收,所以更重要的是知道它已经尽力了,剩下的对象确实仍然被某种方式引用。我的目标是不浪费时间在检查所有引用上,因为这些引用最终会被GC清理掉。 - Gnoupi

1

基本上,你所能做的就是这样:

Runtime r = Runtime.getRuntime();
r.gc();

然而,请记住,所有可以被访问到的引用都被认为是活动的,即使你认为它们不是。根据确切的垃圾回收和设置,这可能会或可能不会收集(大部分)不是“活动”的内存。


当然,除了弱引用(weak references)之外。 - Donal Fellows
2
OP已经知道这一点。Runtime#gc()不会强制执行GC。它最多只是给JVM一个提示。JVM可能实际上并不执行GC。通常情况下,它不会执行。 - BalusC
是的,确切地说,要找到那些“即使你认为它们不是”的对象,我需要确信“垃圾回收器已经尽其所能”。我可以从分析器中调用gc()函数,所以这不是问题。问题只在于达到一个“确定仍然被引用吗?”的状态。 - Gnoupi
“虽然调用垃圾收集器是不够的,而且似乎也没有保证它会清理什么。” - matt b

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