在回答了一个关于如何使用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()实际上是一种平台特定形式的优化(其中“平台”是硬件架构、OS、JVM版本和可能的更多运行时参数(如可用的RAM)的组合),因为它的行为虽然在特定平台上大致可预测,但在不同平台之间可能(也将)有很大差异。
是的,在某些情况下System.gc()将提高(可感知的)性能。举个例子,如果延迟在你的应用的某些部分是可以容忍的,但在其他部分却不能(就像上文所提到的游戏例子,你希望GC发生在关卡开始时,而不是在关卡进行时)。
然而,它是帮助还是伤害(或什么都不做)在很大程度上取决于平台(如上所定义)。
所以我认为这是针对特定平台的最后一种优化方法(即如果其他性能优化还不够的话)。但是,您绝不应该仅仅因为相信它可能有帮助(没有特定的基准)就调用它,因为它很可能没有帮助。
根据我的经验,使用System.gc()实际上是一种平台特定形式的优化(其中“平台”是硬件架构、OS、JVM版本和可能的更多运行时参数(如可用的RAM)的组合),因为它的行为虽然在特定平台上大致可预测,但在不同平台之间可能(也将)有很大差异。
是的,在某些情况下System.gc()将提高(可感知的)性能。举个例子,如果延迟在你的应用的某些部分是可以容忍的,但在其他部分却不能(就像上文所提到的游戏例子,你希望GC发生在关卡开始时,而不是在关卡进行时)。
然而,它是帮助还是伤害(或什么都不做)在很大程度上取决于平台(如上所定义)。
所以我认为这是针对特定平台的最后一种优化方法(即如果其他性能优化还不够的话)。但是,您绝不应该仅仅因为相信它可能有帮助(没有特定的基准)就调用它,因为它很可能没有帮助。
每个人总是说要避免System.gc()的原因是,它是一个很好的指示器,显示出从根本上坏掉的代码。任何依赖于它的正确性的代码肯定是坏的;任何依赖于它的性能都很可能是坏的。
您不知道您正在哪种垃圾收集器下运行。当然,有一些jvm并没有像您断言的那样“停止世界”,但是有些jvm并没有那么聪明,或者由于各种原因(也许它们在电话上?)没有做到这一点。你不知道它会做什么。
而且,它不能保证做任何事情。JVM可能会完全忽略您的请求。
“你不知道它会做什么”,“你甚至不知道它是否有用”,以及“你无论如何都不需要调用它”,这就是为什么人们如此强烈地说,一般来说你不应该调用它。我认为这是一个“如果你需要问你是否应该使用这个,你不应该”的案例
EDIT来解决其他线程的一些问题:
在阅读了你链接的帖子后,还有一些事情我想指出来。
首先,有人建议调用gc()可能会向系统返回内存。这当然不一定是正确的——Java堆本身的增长独立于Java分配。
例如,JVM将保留内存(几十兆字节),并根据需要增加堆。即使在释放Java对象时,它也不一定会将内存返回给系统;保留已分配的内存以供将来的Java分配使用是完全自由的。
为了显示System.gc()可能什么也不做,请查看
JDK bug 6668279
特别是有一个-XX:DisableExplicitGC VM选项:
默认情况下,对System.gc()的调用是启用的(-XX:-DisableExplicitGC)。使用-XX:+DisableExplicitGC禁用对System.gc()的调用。请注意,JVM在必要时仍然执行垃圾收集。
很多人似乎都告诉你不要这样做。我不同意。如果在加载关卡等大型加载过程后,你认为:
您有很多不可访问的对象,可能还没有被gc - ed。而且
您认为此时用户可以忍受轻微的减速
调用System.gc()没有害处。我把它看作c/c++的内联关键字。这只是对gc的一个提示,即您(开发人员)已经决定时间/性能不像通常那样重要,其中一些时间/性能可以用于回收内存。
建议不要依赖它做任何事情是正确的。不要依赖于它的工作,但给一个提示,现在是一个可以接受的时间收集是完全可以的。我宁愿把时间浪费在代码中无关紧要的地方(加载屏幕),也不愿浪费在用户与程序积极互动的时候(比如在游戏关卡中)。
有一次,我将强制收集:当试图找出是一个特定的对象泄漏(本机代码或大型,复杂的回调交互。哦,还有任何UI组件,哪怕只是瞥了一眼Matlab。)在产品代码中不应该使用这种方法。
前面已经解释过,调用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实现使用这种方法…理由很充分。