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


326

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

有人告诉我这是一种不好的做法,但是后来我又被告知,垃圾收集器的运行不再系统地停止世界,而且它也可以有效地被JVM用作提示,所以我有点不知所措。

我确实了解到,JVM在需要回收内存时通常比您了解更多。我也了解担心数千字节的数据是愚蠢的。我还了解到,即使是数百万字节的数据也已不是几年前的样子。但还是1.5 GB?而且您知道内存中大约有1.5 GB的数据悬空。这不像是在黑暗中拍摄。是System.gc()系统上的问题,还是在某种程度上可以解决?

因此,问题实际上是双重的:

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

4
我认为调用System.gc()的好时机是在执行已经很长时间的加载过程时。例如,我正在开发一个游戏,并计划在加载过程结束时在游戏加载新关卡时调用System.gc()。用户已经在等待一点,额外的性能提升可能是值得的。但我还将在配置屏幕中放置一个选项以禁用此行为。
Ricket 2010年

41
博佐:请注意问题的第一个词。为什么最好的做法是不打电话呢?只是重复咒语并不能解释该死的事情。
我的正确观点

1
java-monitor.com/forum/showthread.php?t=188 中已经解释了另一种情况,该 示例说明了如何调用System.gc()可能是一种不好的做法
Shirishkumar Bari 2014年

Answers:


243

每个人都总是要避免的原因System.gc()是,这从根本上破坏了代码。依靠它来确保正确性的任何代码肯定会损坏;任何依靠它来提高性能的方法都很可能被破坏。

您不知道要运行哪种垃圾收集器。当然,有些JVM不能像您断言的那样“阻止世界”,但是某些JVM并不那么聪明,或者由于各种原因(也许它们在电话上?)没有做到这一点。您不知道该怎么办。

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

人们通常这么大胆地说:“您不知道它会做什么,”“您甚至不知道它是否会帮助”,以及“无论如何都不必称呼它”的组合。你不应该这样称呼它。我认为这是“如果您需要询问是否应该使用此功能,则不应该使用”的情况


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

阅读了您链接的线程后,我还要指出几件事。首先,有人建议调用gc()可以将内存返回给系统。这当然不一定是正确的-Java堆本身独立于Java分配而增长。

与之类似,JVM将保留内存(数十兆字节)并根据需要增加堆。即使释放Java对象,它也不一定会将该内存返回给系统。完全免费地保留已分配的内存以用于将来的Java分配。

为了表明可能System.gc()不执行任何操作,请查看:

http://bugs.sun.com/view_bug.do?bug_id=6668279

特别是-XX:DisableExplicitGC VM选项。


1
您也许可以构建一些奇怪的Rube Goldberg式设置,在其中运行GC的方法会影响代码的正确性。也许它掩盖了一些奇怪的线程交互,或者终结器对程序的运行产生了重大影响。我并不完全确定有可能,但有可能,所以我想我提到了。
史蒂文·斯兰斯克

2
@zneak,例如,您可能已将关键代码放入终结器中(这基本上是破碎的代码)
Martin

3
我想补充一点,在一些极端的情况下System.gc()有用甚至可能是必要的。例如,在Windows上的UI应用程序中,如果最小化窗口之前调用System.gc()它可以极大地加快窗口的还原过程(尤其是当窗口保持最小化相当长的时间并且部分过程被交换到该窗口时)磁盘)。
Joachim Sauer 2010年

2
@AndrewJanke我想说的WeakReference是,对于要保留的对象使用s的代码从一开始就是正确的,不管是否是垃圾回收。在C ++中,您将遇到相同的问题std::weak_ptr(尽管您可能会注意到C ++版本中的问题早于Java版本中的问题,因为对象破坏不会像完成终结那样被推迟)。
JAB

2
@rebeccah这是一个错误,所以是的,我将其称为“肯定已损坏”。这事实上System.gc()修复它是一个解决办法,而不是良好的编码习惯。
史蒂文·斯兰斯克

149

已经说明了调用system.gc() 可能无济于事,并且“需要”垃圾收集器运行的任何代码都已损坏。

但是,称其为不好的做法的务实原因System.gc()是效率低下。在最坏的情况下,它效率极低!让我解释。

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

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

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

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

第二个呼叫将(live* W1 + 0 * W2)起作用,并且不回收任何内容。换句话说,我们已经完成了(live * W1)工作,却一无所获

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

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

注意:以上解释掩盖了以下事实:典型的现代GC将堆划分为“空间”,GC可能动态扩展堆,应用程序的非垃圾对象的工作集可能会发生变化等等。即使这样,所有基本垃圾收集器2的基本原理仍然相同。强制GC运行效率低下。


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

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


45
+1很好的解释。但是请注意,这种推理仅在您关心吞吐量时才适用。如果要在特定点上优化潜伏期,则强制使用GC可能很有意义。例如,从假设的角度来说,在游戏中,您可能希望避免在关卡过程中出现延迟,但您并不在意关卡加载过程中的延迟。然后在级别加载后强制GC是有意义的。它确实会降低总体吞吐量,但这不是您要优化的。
sleske 2010年

2
@sleske-您所说的是正确的。但是,在级别之间运行GC是一个创可贴解决方案...如果级别花费的时间足够长,无论如何您都需要在级别上运行GC,这不能解决延迟问题。更好的方法是使用并发(低暂停)垃圾收集器...如果平台支持的话。
斯蒂芬·C

不能完全确定“需要运行垃圾收集器”的含义。应用程序需要避免的条件是:永远无法满足的分配失败和较高的GC开销。随机调用System.gc()通常会导致较高的GC开销。
柯克2014年

@Kirk- “随机调用System.gc()通常会导致较高的GC开销。” 。我知道。阅读并理解我的答案的人也会如此。
Stephen C

@Kirk- “不完全确定您的意思...” -我的意思是程序的“正确”行为取决于在特定时间运行的GC的程序;例如,为了执行终结器或破坏WeakReferences或SoftReferences。这样做是一个非常糟糕的主意……但这就是我在说的。另请参阅史蒂文·斯兰斯克(Steven Schlansker)的答案。
Stephen C

47

很多人似乎在告诉您不要这样做。我不同意。如果在像加载关卡这样的大型加载过程之后,您认为:

  1. 您有许多无法访问的对象,可能还没有被gc'ed处理。和
  2. 您认为用户此时可以忍受一点点减速

调用System.gc()没有任何害处。我将其视为c / c ++ inline关键字。这只是向gc的提示,您(开发人员)已经决定时间/性能并不像平时那样重要,并且其中的一些时间可以用来回收内存。

建议不要依赖它做任何事情都是正确的。不要依靠它来工作,但是提示现在是可以接受的收集时间是非常好的。我宁愿在代码中无关紧要的时间(加载屏幕)上浪费时间,而不是在用户与程序进行积极交互时(例如在玩游戏时)浪费时间。

有一次,我将强制收集:尝试找出特定的对象泄漏(本机代码或大型,复杂的回调交互。哦,以及任何看过Matlab的UI组件。)永远不要使用在生产代码中。


3
分析内存泄漏时为GC +1。请注意,有关堆使用情况的信息(Runtime.freeMemory()等)实际上仅在强制执行GC之后才有意义,否则,将取决于系统上次为运行GC花费的时间。
sleske 2010年

4
调用System.gc()可能不需要stop the world采取任何措施,这是一种真正的危害,如果发生的话
bestsss 2011-02-09

7
在显式调用垃圾收集器的危害。在错误的时间调用GC会浪费CPU周期。您(程序员)没有足够的信息来确定何时是正确的时间,但是JVM拥有。
Stephen C

在启动后,Jetty会对System.gc()进行2次调用,但我很少看到它有什么作用。
柯克

那么,Jetty开发人员有一个错误。即使差异难以量化,也会有所作为。
Stephen C

31

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

(以下注释适用于使用CMS收集器在Linux上运行的Hotspot,我可以肯定地说System.gc()实际上总是调用完整的垃圾收集)。

  1. 在启动应用程序的初步工作之后,您可能会陷入内存使用的糟糕状态。终身使用的一代中有一半可能充满了垃圾,这意味着您距离第一个CMS更近了。在重要的应用程序中,调用System.gc()将堆“重置”为活动数据的起始状态并不是一个坏主意。

  2. 与#1相同,如果您密切监视堆使用情况,则希望准确了解基线内存使用情况。如果应用程序正常运行的前2分钟都是初始化,那么除非您强行(建议...“建议”)完整的gc,否则您的数据将被弄乱。

  3. 您可能有一个应用程序设计为永远不会将任何东西升级为终身代。但是,也许您需要预先初始化一些不太大的数据,以便自动移交给有期限的一代。除非在完成所有设置后调用System.gc(),否则您的数据可能会处于新一代状态,直到有时间对其进行升级为止。突然之间,您的超延迟,低延迟,低GC应用程序会因在正常操作期间提升这些对象而遭受巨大的(当然,相对而言)延迟损失。

  4. 有时在生产应用程序中提供System.gc调用对验证内存泄漏的存在很有用。如果您知道时间X处的实时数据集应与时间Y处的实时数据集存在一定比例,则将System.gc()分别调用时间X和时间Y并比较内存使用情况可能很有用。 。


1
对于大多数世代垃圾收集器而言,新一代的对象必须保留一定数量(通常是可配置的)垃圾收集,因此调用System.gc()一次强制提升对象不会为您带来任何好处。您肯定不想System.gc()连续打过八次电话,现在祈祷,升级已经完成,以后的升级节省下来的成本证明了多个完整GC的成本是合理的。根据GC算法的不同,提升很多对象甚至可能不承担实际成本,因为这只会将内存重新分配给旧一代或同时复制…
Holger

11

这是一个非常烦人的问题,尽管它是一种有用的语言,但我认为许多人都反对Java。

您不信任“ System.gc”执行任何操作这一事实令人难以置信,并且可以轻松地对该语言调用“恐惧,不确定性,怀疑”感觉。

在许多情况下,最好在重要事件发生之前处理您有意引起的内存高峰,这会导致用户认为您的程序设计不良/无响应。

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

让我回顾一下该线程的参数。

  1. 它效率低下:

通常,该程序可能没有执行任何操作,并且您知道由于设计方式它没有执行任何操作。例如,它可能正在使用大型的等待消息框进行某种长时间的等待,并且最后还可能添加一个调用以收集垃圾,因为运行它所花费的时间仅占该时间的一小部分。漫长的等待时间,但可以避免gc在更重要的操作中起作用。

  1. 这始终是一种不良做法,并指出代码已损坏。

我不同意,您拥有什么垃圾收集器都没有关系。它的工作是跟踪垃圾并进行清理。

通过在不太重要的使用情况下调用gc,可以减少当您的生活依赖于所运行的特定代码而决定收集垃圾时运行它的几率。

当然,它可能无法按照您期望或期望的方式运行,但是当您确实要调用它时,您什么也没有发生,并且用户愿意忍受缓慢/停机时间。如果System.gc有效,那就太好了!如果没有,至少您尝试过。除非垃圾收集器具有固有的副作用,否则如果使用手动调用垃圾收集器会产生什么样的行为,这简直是不利的,这本身会引起不信任。

  1. 这不是常见的用例:

这是一个无法可靠实现的用例,但是如果系统是按这种方式设计的,则可能是这样。这就像制作一个交通信号灯并使它的某些/所有交通信号灯的按钮什么都不做,这使您质疑为什么按钮从一开始就没有,javascript没有垃圾收集功能,所以我们不这样做对此不多加审查。

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

什么是“提示”?什么是“忽略”?计算机不能简单地获取提示或忽略某些内容,它所采取的严格的行为路径可能是动态的,并且受系统意图的引导。正确的答案应该包括垃圾收集器在实现级别实际正在做什么,这将导致它在您请求时不执行收集。该功能仅仅是点头吗?我必须满足一些条件吗?这些条件是什么?

就目前而言,Java的GC通常看起来就像是您不信任的怪物。您不知道它什么时候来或去,您不知道它会做什么,怎么做。我可以想象一些专家对垃圾收集如何按指令工作有更好的了解,但绝大多数人只是希望它“有效”,而不得不依靠一个看起来不透明的算法来为您工作是令人沮丧的。

在阅读或学习某些东西与实际看到它的实现,整个系统之间的差异以及无需查看源代码就可以使用它之间存在很大的差距。这会产生自信和掌握/理解/控制的感觉。

总而言之,答案存在一个固有的问题:“此功能可能无法执行任何操作,而我不会详细介绍如何分辨何时执行某项操作,何时不执行某项操作以及为什么它不会或不会做任何事情,常常暗示试图这样做是完全违背哲学的,即使其意图是合理的。”

Java GC可以按照其行为方式行事,也可以不行,但是要理解它,很难真正地沿着哪个方向去获得对您可以信任的GC所做的事情的全面概述,以及如果不这样做,那么简单地不信任该语言就太容易了,因为语言的目的是在哲学范围内控制行为(对于程序员,特别是新手来说,很容易因某些系统/语言行为而陷入生存危机)能够忍受(如果不能的话,除非有必要,否则您将不会使用该语言),而且由于未知原因无法控制而无法控制的其他事情本质上是有害的。


9

GC效率取决于多种启发式方法。例如,一种常见的启发式方法是,对对象的写访问通常发生在不久前创建的对象上。另一个是许多对象的寿命很短(某些对象将使用很长时间,但许多对象在创建后将被丢弃几微秒)。

呼叫System.gc()就像踢GC。意思是:“所有那些经过仔细调整的参数,那些精明的组织,您刚刚在分配和管理对象上所做的所有努力,以使事情顺利进行,顺利进行,只需全部删除并从头开始。” 它可能会提高性能,但大多数情况下只会降低性能。

为了System.gc()可靠地使用(*),您需要知道GC如何在所有细节上运行。如果您使用其他供应商的JVM,或者使用同一供应商的下一个版本,或者使用相同的JVM,但命令行选项稍有不同,则这些详细信息可能会发生很大变化。因此,除非您想解决一个控制所有这些参数的特定问题,否则这几乎不是一个好主意。因此,“不良做法”的概念:这是不被禁止的,该方法存在,但很少能奏效。

(*)我在这里谈论效率。System.gc()永远不会破坏正确的Java程序。它既不会产生JVM否则无法获得的额外内存:在抛出之前OutOfMemoryError,JVM会做的工作System.gc(),即使万不得已。


1
+1表示System.gc()不能防止OutOfMemoryError。有人相信这一点。
sleske 2010年

1
实际上,由于软引用的处理,它可以防止OutOfMemoryError。我最后一次运行GC之后创建的SoftReferences没有收集到我知道的实现中。但这是随时可能更改的实现细节,并且是一种错误,您不应依赖。
maaartinus

8

有时(不是经常!)您确实比运行时更了解过去,当前和将来的内存使用情况。这种情况很少发生,在提供正常页面的情况下,我绝对不会在Web应用程序中声明。

很多年前,我在一个报告生成器上工作,

  • 只有一个线程
  • 从队列中读取“报告请求”
  • 从数据库中加载了报表所需的数据
  • 生成报告并通过电子邮件发送出去。
  • 永远重复一次,没有任何未解决的请求时就睡觉。
  • 它没有重用报告之间的任何数据,也没有做任何现金。

首先,由于它不是实时的,并且用户希望等待报告,因此GC运行不是一个延迟,而是一个问题,但是我们需要以比要求的速度更快的速度来生成报告。

通过以上过程概述,可以很明显地看出这一点。

  • 我们知道在通过电子邮件发送报告之后,活动对象将很少,因为下一个请求尚未开始处理。
  • 众所周知,运行垃圾回收周期的成本取决于活动对象的数量,垃圾量对GC运行的成本影响很小。
  • 当队列为空时,再没有其他操作可以运行GC了。

因此,很明显,每当请求队列为空时,进行GC运行都是值得的。没有不利的一面。

在通过电子邮件发送每个报告之后进行GC运行可能是值得的,因为我们知道这是进行GC运行的好时机。但是,如果计算机具有足够的内存,则可以通过延迟GC运行来获得更好的结果。

此行为是在每个安装基础上配置的,对于某些客户,在每个报告极大地加快了报告的保护之后,启用了强制GC 。(我希望这是由于服务器上的内存不足,并且它正在运行许多其他进程,因此,及时执行GC可以减少分页。)

我们从未发现每次工作队列为空时强制运行GC都会使安装无益。

但是,请明确地说,上述情况并不常见。


3

也许我编写了糟糕的代码,但是我已经意识到,单击eclipse和netbeans IDE上的垃圾桶图标是一种“好习惯”。


1
可能是这样。但是,如果将Eclipse或NetBeans编程为System.gc()定期调用,您可能会发现该行为很烦人。
Stephen C

2

首先,规范与现实之间存在差异。规范说System.gc()暗示GC应该运行,而VM可以忽略它。现实情况是,VM 永远不会忽略对System.gc()的调用。

呼叫GC会给您带来一笔不小的开销,如果您在某个随机的时间点执行此操作,则很可能看不到任何回报。另一方面,自然触发的收款很可能弥补通话费用。如果您有表明应该运行GC的信息,则可以调用System.gc()并看到好处。但是,根据我的经验,这种情况仅在少数情况下会发生,因为您不太可能拥有足够的信息来了解是否以及何时应调用System.gc()。

此处列出的一个示例是在您的IDE中击中垃圾桶。如果您要去开会,为什么不参加会议。开销不会影响您,回来时可能会清理堆。在生产系统中执行此操作,频繁的收集呼叫将使其停顿!即使是偶尔的呼叫(例如RMI发出的呼叫)也会破坏性能。


3
“现实是,VM永远不会忽略对System.gc()的调用。” -不正确。阅读有关-XX:-DisableExplicitGC的信息。
斯蒂芬·C

1

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

调用gc方法表明Java虚拟机在回收未使用的对象上花费了很多精力。

调用它几乎总是一个坏主意,因为自动内存管理通常比您何时使用gc更了解。当其内部可用内存不足或操作系统要求退回一些内存时,它将这样做。

如果您知道有帮助,可以调用System.gc()。我的意思是,您已经在部署平台上彻底测试和测量了这两种方案的行为,并且可以证明它对您有所帮助。请注意,尽管gc难以预测-在一次运行中可能会有所帮助,而在另一次运行中可能会受到伤害。


<stroke>但也来自Javadoc:_当控件从方法调用中返回时,虚拟机将尽最大努力回收所有丢弃的对象,我认为这是发布内容的更必要的形式。</ stroke >解决这个问题,有一个关于它误导性的错误报告。从哪个了解的更多,提示JVM有什么害处?
zneak 2010年

1
这样做的危害是在错误的时间进行收集会大大减慢速度。您给出的提示可能是一个不好的提示。至于“尽力而为”的注释,请尝试一下并在类似JConsole的工具中查看。有时单击“执行GC”按钮无济于事
tom

很抱歉不同意,但是在OpenJDK中调用System.gc()以及基于它的任何内容(例如HP)始终会导致垃圾回收周期。事实上,似乎IBM的J9实施也是如此
柯克

@Kirk-不正确:google,并阅读有关-XX:-DisableExplicitGC的信息。
Stephen C

0

以我的经验,使用System.gc()实际上是特定于平台的优化形式(其中“平台”是硬件体系结构,操作系统,JVM版本以及可能的更多运行时参数(如可用的RAM)的组合),因为它的行为是,虽然可以在特定平台上大致预测,但是在各个平台之间可能(并且将会有很大不同)。

是的,在某些情况下System.gc()会提高(感知)性能。例如,在应用程序的某些部分是否可以忍受延迟,而在其他部分则不能忍受(上面引用的游戏示例,您希望GC在关卡的开始而不是在关卡期间发生)。

但是,是帮助还是伤害(或什么都不做)高度依赖于平台(如上定义)。

因此,我认为这是针对特定平台的最后手段的优化(即,如果其他性能优化还不够的话)。但是您永远不要仅仅因为您认为它可能会有所帮助(没有特定的基准)而就称呼它,因为有可能它不会。


0
  1. 由于使用new运算符动态分配了对象,因此
    您可能想知道如何销毁这些对象并
    释放它们的内存以供以后重新分配。

  2. 在某些语言(例如C ++)中,必须使用delete运算符手动释放动态分配的对象。

  3. Java采用了不同的方法。它会自动为您处理释放。
  4. 实现此目的的技术称为垃圾收集。它的工作原理是:当不存在对任何对象的引用时,假定不再需要该对象,并且可以回收该对象占用的内存。不需要像C ++中那样破坏对象。
  5. 垃圾收集仅在程序执行期间偶尔(如果有的话)发生。
  6. 它不会仅仅因为存在一个或多个不再使用的对象而发生。
  7. 此外,不同的Java运行时实现将采用不同的方法来进行垃圾回收,但是在大多数情况下,您在编写程序时不必考虑它。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.