在回答了一个关于如何使用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()提示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偶尔发出的调用也会对性能造成破坏。
根据我的经验,使用System.gc()实际上是一种平台特定形式的优化(其中“平台”是硬件架构、OS、JVM版本和可能的更多运行时参数(如可用的RAM)的组合),因为它的行为虽然在特定平台上大致可预测,但在不同平台之间可能(也将)有很大差异。
是的,在某些情况下System.gc()将提高(可感知的)性能。举个例子,如果延迟在你的应用的某些部分是可以容忍的,但在其他部分却不能(就像上文所提到的游戏例子,你希望GC发生在关卡开始时,而不是在关卡进行时)。
然而,它是帮助还是伤害(或什么都不做)在很大程度上取决于平台(如上所定义)。
所以我认为这是针对特定平台的最后一种优化方法(即如果其他性能优化还不够的话)。但是,您绝不应该仅仅因为相信它可能有帮助(没有特定的基准)就调用它,因为它很可能没有帮助。
GC效率依赖于许多启发式方法。例如,一个常见的启发是,对对象的写访问通常发生在不久前创建的对象上。另一个原因是许多对象的寿命非常短(有些对象会使用很长时间,但许多对象在创建后几微秒就会被丢弃)。
调用System.gc()就像踢掉GC。它的意思是:“所有那些精心调整的参数,那些聪明的组织,所有你投入到分配和管理对象上的努力,让事情顺利进行,好吧,放弃所有这些,从头开始”。它可以提高性能,但大多数时候它只会降低性能。
要可靠地(*)使用System.gc(),您需要了解GC的所有细节。如果使用其他供应商的JVM,或者使用同一供应商的下一个版本,或者使用相同JVM但命令行选项略有不同,那么这些细节可能会发生很大变化。因此,这很少是一个好主意,除非你想解决一个你控制所有这些参数的特定问题。因此就有了“坏做法”的概念:这并没有被禁止,方法是存在的,但它很少有回报。
我在这里谈论的是效率。System.gc()永远不会破坏正确的Java程序。它既不会产生JVM无法获得的额外内存:在抛出OutOfMemoryError之前,JVM会执行System.gc()的工作,即使是作为最后的手段。
有时(不是经常!)您确实比运行时更了解过去、当前和将来的内存使用情况。这种情况并不经常发生,而且我敢说,在web应用程序中,当提供正常页面时,这种情况绝不会发生。
很多年前,我在一个报告生成器上工作
只有一根线
从队列中读取“报告请求”
从数据库加载报告所需的数据
生成报告并通过电子邮件发送出去。
没完没了地重复,没有特别的要求就睡去。
它没有在报告之间重复使用任何数据,也没有进行任何兑现。
首先,因为它不是实时的,而且用户希望等待报告,GC运行时的延迟不是问题,但是我们需要以比请求更快的速度生成报告。
看了上面的过程大纲,很明显。
我们知道,在报告通过电子邮件发送出去之后,活动对象会非常少,因为下一个请求还没有开始处理。
众所周知,运行垃圾收集周期的成本取决于活动对象的数量,垃圾的数量对GC运行的成本几乎没有影响。
当队列为空时,没有什么更好的事情可做,然后运行GC。
因此,当请求队列为空时执行GC运行显然是非常值得的;这并没有什么坏处。
在每个报告通过电子邮件发送之后执行GC运行可能是值得的,因为我们知道这是GC运行的好时机。但是,如果计算机有足够的ram,则可以通过延迟GC运行来获得更好的结果。
这种行为是在每个安装基础上配置的,对于一些客户来说,在每个报告之后启用强制GC可以大大加快报告的生成速度。(我认为这是由于他们服务器上的内存较低,并且运行了许多其他进程,因此强制GC减少了分页。)
每次工作队列为空时,我们从未检测到一个安装没有从强制GC运行中获益。
但是,需要明确的是,上述情况并不常见。
现在,我更倾向于在单独的进程中运行每个报告,让操作系统清理内存,而不是使用垃圾收集器,并让自定义队列管理器服务在大型服务器上使用多个工作进程。
很多人似乎都告诉你不要这样做。我不同意。如果在加载关卡等大型加载过程后,你认为:
您有很多不可访问的对象,可能还没有被gc - ed。而且
您认为此时用户可以忍受轻微的减速
调用System.gc()没有害处。我把它看作c/c++的内联关键字。这只是对gc的一个提示,即您(开发人员)已经决定时间/性能不像通常那样重要,其中一些时间/性能可以用于回收内存。
建议不要依赖它做任何事情是正确的。不要依赖于它的工作,但给一个提示,现在是一个可以接受的时间收集是完全可以的。我宁愿把时间浪费在代码中无关紧要的地方(加载屏幕),也不愿浪费在用户与程序积极互动的时候(比如在游戏关卡中)。
有一次,我将强制收集:当试图找出是一个特定的对象泄漏(本机代码或大型,复杂的回调交互。哦,还有任何UI组件,哪怕只是瞥了一眼Matlab。)在产品代码中不应该使用这种方法。