为什么调用 System.gc() 是不好的做法?

2022-08-31 04:48:38

回答了一个关于如何在Java中强制释放对象的问题(那个家伙正在清除一个1.5GB的HashMap)之后,我被告知手动调用是不好的,但这些评论并不完全令人信服。此外,似乎没有人敢为我的答案投赞成票,也不敢投反对票。System.gc()System.gc()

我在那里被告知这是不好的做法,但后来我也被告知垃圾收集器运行不再系统地阻止世界,并且它也可以被JVM有效地用作提示,所以我有点不知所措。

我确实知道JVM在需要回收内存时通常比您更清楚。我也明白,担心几千字节的数据是愚蠢的。我也明白,即使是兆字节的数据也不是几年前的样子。但是,1.5千兆字节?而且您知道内存中悬挂着大约1.5 GB的数据;这不像是在黑暗中拍摄的。是系统性的坏事,还是在某些时候它变得没问题?System.gc()

所以问题实际上是双重的:

  • 为什么打电话是或不是不好的做法?它真的只是在某些实现下对JVM的提示,还是总是一个完整的收集周期?真的有垃圾回收器实现可以在不阻止世界的情况下完成他们的工作吗?请阐明人们在评论我的答案时提出的各种断言。System.gc()
  • 门槛在哪里?打电话从来不是一个好主意,还是有时可以接受?如果是这样,这些时间是什么?System.gc()

答案 1

每个人都总是说要避免的原因是,它是从根本上破坏代码的一个很好的指标。任何依赖它来获得正确性的代码肯定会被破坏;任何依赖它来获得性能的东西都很可能被破坏了。System.gc()

您不知道您正在运行哪种垃圾回收器。当然,有些JVM并不像你断言的那样“阻止世界”,但有些JVM并不那么聪明,或者由于各种原因(也许他们在手机上?)没有这样做。你不知道它会做什么。

此外,它不能保证做任何事情。JVM 可能完全忽略您的请求。

“你不知道它会做什么”,“你不知道它是否会有所帮助”和“你无论如何都不需要打电话给它”的组合是为什么人们如此强硬地说,一般来说你不应该打电话给它。我认为这是一个“如果你需要问你是否应该使用这个,你不应该”的情况。


编辑以解决其他线程的一些问题:

在阅读了您链接的帖子之后,我想指出更多的事情。首先,有人建议调用可能会将内存返回到系统。这当然不一定是真的 - Java堆本身独立于Java分配而增长。gc()

与 IN 一样,JVM 将保存内存(数十兆字节)并根据需要增加堆。即使您释放Java对象,它也不一定将该内存返回给系统;它完全可以自由地保留分配的内存以用于将来的Java分配。

为了证明它可能什么都不做,请查看JDK bug 6668279,特别是有一个-XX:DisableExplicitGC VM选项:System.gc()

默认情况下,对 的调用处于启用状态 ()。用于禁用对 的调用。请注意,JVM 在必要时仍会执行垃圾回收。System.gc()-XX:-DisableExplicitGC-XX:+DisableExplicitGCSystem.gc()


答案 2

已经解释过,调用可能不执行任何操作,并且任何“需要”垃圾回收器运行的代码都已损坏。system.gc()

然而,打电话是不好的做法的务实原因是它效率低下。在最坏的情况下,它的效率非常低下!让我解释一下。System.gc()

典型的 GC 算法通过遍历堆中的所有非垃圾对象来识别垃圾,并推断任何未访问的对象都必须是垃圾。由此,我们可以对垃圾回收的总工作量进行建模,该工作由一部分与实时数据量成比例,另一部分与垃圾量成比例;即 .work = (live * W1 + garbage * W2)

现在假设您在单线程应用程序中执行以下操作。

System.gc(); System.gc();

第一个调用将(我们预测)完成工作,并摆脱未完成的垃圾。(live * W1 + garbage * W2)

第二个调用将执行任何操作并回收任何内容。换言之,我们做了工作,但一事无成(live* W1 + 0 * W2)(live * W1)

我们可以将收集器的效率建模为收集单位垃圾所需的工作量;即 .因此,为了使GC尽可能高效,我们需要最大化运行GC时的价值;即等到堆已满。(另外,使堆尽可能大。但这是一个单独的话题。efficiency = (live * W1 + garbage * W2) / garbagegarbage

如果应用程序不干扰(通过调用),GC 将等到堆已满后再运行,从而有效地收集垃圾1。但是,如果应用程序强制 GC 运行,则堆可能会不会满,结果将是垃圾收集效率低下。应用程序强制GC的频率越高,GC的效率就越低。System.gc()

注意:上面的解释掩盖了这样一个事实,即典型的现代GC将堆划分为“空间”,GC可能会动态扩展堆,应用程序的非垃圾对象的工作集可能会有所不同等等。即便如此,相同的基本原理全面适用于所有真正的垃圾回收器2。强制 GC 运行是低效的。


1 - 这就是“吞吐量”收集器的工作原理。CMS 和 G1 等并发收集器使用不同的条件来决定何时启动垃圾回收器。

2 - 我还排除了专门使用引用计数的内存管理器,但当前没有Java实现使用这种方法......这是有充分理由的。