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

我在那里被告知这是一种糟糕的做法,但后来我又被告知垃圾收集器的运行不再系统地停止整个世界,而且JVM也只能有效地将其用作提示,所以我有点不知所措。

我知道JVM通常比您更了解何时需要回收内存。我也明白,担心几千字节的数据是愚蠢的。我也明白,即使是兆字节的数据也不如几年前了。但还是1.5 gb ?你知道大概有1.5 GB的数据在内存中;这又不是瞎猜的。System.gc()在系统上是坏的,还是在某个点上变得正常了?

所以这个问题实际上是双重的

Why is or isn't it bad practice to call System.gc()? Is it really merely a hint to the JVM under certain implementations, or is it always a full collection cycle? Are there really garbage collector implementations that can do their work without stopping the world? Please shed some light over the various assertions people have made in the comments to my answer. Where's the threshold? Is it never a good idea to call System.gc(), or are there times when it's acceptable? If so, what are those times?


当前回答

前面已经解释过,调用system.gc()可能什么都不做,任何“需要”垃圾收集器运行的代码都是坏的。

然而,调用System.gc()是一种糟糕的实践,其实际原因是它效率低下。在最坏的情况下,它的效率非常低!让我解释一下。

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

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

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

第一个调用将(我们预测)做(活* W1 +垃圾* W2)工作,并摆脱未处理的垃圾。

第二个调用将执行(live* W1 + 0 * W2)工作,并且不回收任何东西。换句话说,我们做了(活的)工作,却一事无成。

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

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

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


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

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

其他回答

前面已经解释过,调用system.gc()可能什么都不做,任何“需要”垃圾收集器运行的代码都是坏的。

然而,调用System.gc()是一种糟糕的实践,其实际原因是它效率低下。在最坏的情况下,它的效率非常低!让我解释一下。

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

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

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

第一个调用将(我们预测)做(活* W1 +垃圾* W2)工作,并摆脱未处理的垃圾。

第二个调用将执行(live* W1 + 0 * W2)工作,并且不回收任何东西。换句话说,我们做了(活的)工作,却一事无成。

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

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

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


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

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

是的,调用System.gc()并不能保证它会运行,它是对JVM的请求,可能会被忽略。从文档中可以看出:

调用gc方法表明Java虚拟机将精力用于回收未使用的对象

调用它几乎总是一个坏主意,因为自动内存管理通常比您更了解何时进行gc。当它的内部空闲内存池很低时,或者当操作系统要求归还一些内存时,它会这样做。

如果知道System.gc()有帮助,调用它可能是可以接受的。我的意思是,您已经在部署平台上对两个场景的行为进行了彻底的测试和测量,并且可以证明这是有帮助的。但是要注意gc是不容易预测的-它可能在一次运行中有帮助,在另一次运行中有伤害。

人们已经很好地解释了为什么不使用它,所以我将告诉你一些你应该使用它的情况:

(下面的评论适用于在带有CMS收集器的Linux上运行的Hotspot,在这里我有信心地说System.gc()实际上总是调用完整的垃圾收集)。

After the initial work of starting up your application, you may be a terrible state of memory usage. Half your tenured generation could be full of garbage, meaning that you are that much closer to your first CMS. In applications where that matters, it is not a bad idea to call System.gc() to "reset" your heap to the starting state of live data. Along the same lines as #1, if you monitor your heap usage closely, you want to have an accurate reading of what your baseline memory usage is. If the first 2 minutes of your application's uptime is all initialization, your data is going to be messed up unless you force (ahem... "suggest") the full gc up front. You may have an application that is designed to never promote anything to the tenured generation while it is running. But maybe you need to initialize some data up-front that is not-so-huge as to automatically get moved to the tenured generation. Unless you call System.gc() after everything is set up, your data could sit in the new generation until the time comes for it to get promoted. All of a sudden your super-duper low-latency, low-GC application gets hit with a HUGE (relatively speaking, of course) latency penalty for promoting those objects during normal operations. It is sometimes useful to have a System.gc call available in a production application for verifying the existence of a memory leak. If you know that the set of live data at time X should exist in a certain ratio to the set of live data at time Y, then it could be useful to call System.gc() a time X and time Y and compare memory usage.

首先,规范和现实之间是有区别的。规范说System.gc()提示GC应该运行,VM可以忽略它。实际情况是,VM永远不会忽略对System.gc()的调用。

Calling GC comes with a non-trivial overhead to the call and if you do this at some random point in time it's likely you'll see no reward for your efforts. On the other hand, a naturally triggered collection is very likely to recoup the costs of the call. If you have information that indicates that a GC should be run than you can make the call to System.gc() and you should see benefits. However, it's my experience that this happens only in a few edge cases as it's very unlikely that you'll have enough information to understand if and when System.gc() should be called.

这里列出了一个例子,在IDE中敲击垃圾桶。如果你要去开会,为什么不去呢?开销不会影响您,当您返回时,可能会清理堆。在生产系统中执行此操作,频繁调用收集将使其彻底停止!即使是RMI偶尔发出的调用也会对性能造成破坏。

这是一个非常麻烦的问题,我觉得这是许多人反对Java的原因,尽管它是一种多么有用的语言。

你不能相信"系统"gc”来做任何事情都令人难以置信地令人生畏,并且很容易调用“恐惧,不确定,怀疑”的语言感觉。

在许多情况下,在重要事件发生之前处理您故意引起的内存峰值是很好的,这将导致用户认为您的程序设计很糟糕/反应迟钝。

拥有控制垃圾收集的能力将是一个非常好的教育工具,进而提高人们对垃圾收集如何工作以及如何使程序利用其默认行为和受控行为的理解。

让我回顾一下这篇文章的论点。

效率低下:

通常情况下,程序可能什么都不做,而您知道它什么都不做是因为它的设计方式。例如,它可能正在使用一个大的等待消息框进行某种长时间的等待,最后它可能会添加一个调用来收集垃圾,因为运行它的时间只占长等待时间的一小部分,但可以避免gc在更重要的操作中间发生故障。

这是一种不好的做法,表明代码有问题。

我不同意,不管你有什么垃圾收集器。它的工作是追踪垃圾并清理垃圾。

通过在使用不那么关键的时候调用gc,当您的生命依赖于正在运行的特定代码,但它却决定收集垃圾时,您可以减少gc运行的几率。

当然,它的行为可能不是您想要或期望的方式,但当您确实想要调用它时,您知道什么都没有发生,并且用户愿意容忍缓慢/停机。如果系统。Gc工作,太棒了!如果没有,至少你试过了。没有任何缺点,除非垃圾收集器具有固有的副作用,会对手动调用垃圾收集器的行为产生可怕的意想不到的影响,而这本身就会引起不信任。

这不是一个常见的用例:

这是一个不能可靠地实现的用例,但如果系统以这种方式设计,则可以实现。这就像做一个交通灯,让它的一些/所有的交通灯的按钮不做任何事情,这让你质疑为什么按钮在那里开始,javascript没有垃圾收集功能,所以我们没有仔细检查它。

规范说System.gc()提示GC应该运行,VM可以忽略它。

什么是“暗示”?什么是“忽略”?计算机不能简单地接受暗示或忽略某些东西,它所采取的严格行为路径可能是动态的,由系统的意图指导。一个正确的答案应该包括垃圾收集器在实现级别上实际做了什么,导致它在您请求它时不执行收集。这个功能只是一个nop吗?有什么条件是必须满足的吗?这些条件是什么?

就目前的情况而言,Java的GC通常看起来像一个不值得信任的怪物。你不知道它什么时候来,什么时候走,你不知道它会做什么,它会怎么做。我可以想象一些专家对他们的垃圾收集如何在每条指令的基础上工作有更好的想法,但绝大多数人只是希望它“只是工作”,不得不相信一个看起来不透明的算法为你工作是令人沮丧的。

阅读一些东西或学习一些东西,与实际看到它的实现,不同系统之间的差异,以及能够在不查看源代码的情况下使用它之间有很大的差距。这会创造自信和掌控/理解/控制的感觉。

总而言之,“这个功能可能不会做任何事情,我不会详细说明它什么时候会做什么事情,什么时候不会做,为什么不会或会做,这通常意味着尝试这样做是违反哲学的,即使背后的意图是合理的”,这是一个固有的问题。

It might be okay for Java GC to behave the way it does, or it might not, but to understand it, it is difficult to truly follow in which direction to go to get a comprehensive overview of what you can trust the GC to do and not to do, so it's too easy simply distrust the language, because the purpose of a language is to have controlled behavior up to philosophical extent(it's easy for a programmer, especially novices to fall into existential crisis from certain system/language behaviors) you are capable of tolerating(and if you can't, you just won't use the language until you have to), and more things you can't control for no known reason why you can't control them is inherently harmful.