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

时间:2010-03-10 02:19:38

标签: java garbage-collection

answering关于如何force-free objects in Java(该家伙正在清除1.5GB HashMap)与System.gc()的问题后,我被告知手动调用System.gc()是不好的做法,但评论并不完全令人信服。此外,似乎没有人敢于投票,也没有人愿意回答。

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

我明白JVM在需要回收内存时通常比你更了解。我也明白,担心几千字节的数据是愚蠢的。我也明白,甚至数兆字节的数据也不是几年前的数据。但仍然,1.5千兆字节?你知道就像1.5 GB的数据在内存中徘徊;它不像是在黑暗中拍摄。 System.gc()是否系统性地不好,或者是否有某种程度上它变得可以接受?

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

  • 调用System.gc()为什么或不是不好的做法?它是否仅仅是某些实现中JVM的提示,还是一个完整的收集周期?真的有垃圾收集器实现可以在不停止世界的情况下完成工作吗?请对人们在answer的评论中提出的各种断言有所了解。
  • 门槛在哪里?呼叫System.gc() 从不永远是一个好主意,还是有时候可以接受?如果是的话,那些时候是什么时候?

14 个答案:

答案 0 :(得分:235)

每个人总是说避免使用System.gc()的原因是它是基本上破坏代码的非常好的指标。任何依赖于它的正确性的代码肯定会被打破;任何依赖它来提高性能的人都很可能会被打破。

你不知道你正在运行什么样的垃圾收集器。当你断言时肯定会有一些“停止世界”,但是有些JVM不是那么聪明或者出于各种原因(也许是在电话上?)不这样做。你不知道它会做什么。

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

“你不知道它会做什么”,“你不知道它是否会有所帮助”和“你不应该再打电话”的组合是人们如此强大的原因。说通常你不应该叫它。我认为这是“如果你需要问你是否应该使用它,你不应该”


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

在阅读您链接的主题后,还有一些我想指出的内容。 首先,有人建议调用gc()可能会将内存返回给系统。这当然不一定正确 - Java堆本身的增长与Java分配无关。

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

要表明System.gc()可能无效,请查看:

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

,特别是有一个-XX:DisableExplicitGC VM选项。

答案 1 :(得分:143)

已经解释过调用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尽可能高效,我们需要在运行GC时最大化 garbage的值;即等到堆满了。 (而且,尽可能使堆尽可能大。但这是一个单独的主题。)

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

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


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

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

答案 2 :(得分:43)

许多人似乎在告诉你不要这样做。我不同意。如果在加载一个级别之后的大型加载过程中,您认为:

  1. 你有很多无法访问的对象,可能没有gc'ed。和
  2. 您认为此时用户可以忍受小幅放缓
  3. 调用System.gc()没有任何害处。我把它看作c / c ++ inline关键字。这只是gc的一个暗示,你,开发人员已经决定时间/性能不像通常那么重要,而且其中一些可以用来回收内存。

    不依赖它做任何事情的建议是正确的。不要依赖它的工作,但提示现在是一个可接受的时间收集完全没问题。我宁愿浪费时间在代码中无关紧要的地方(加载屏幕),而不是用户积极地与程序交互时(比如在游戏关卡期间)。

    有一次我将force收集:当试图找出特定对象泄漏时(本机代码或大型,复杂的回调交互。哦和任何UI组件,只要看一眼Matlab 。)绝不应在生产代码中使用。

答案 3 :(得分:29)

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

(以下注释适用于使用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并比较内存使用情况会很有用

答案 4 :(得分:9)

GC效率依赖于一些启发式方法。例如,一种常见的启发式方法是对对象的写访问通常发生在不久前创建的对象上。另一个是很多对象都是非常短暂的(有些对象会被使用很长时间,但很多对象会在创建后的几微秒内被丢弃)。

调用System.gc()就像踢GC一样。它意味着:“所有那些经过精心调整的参数,那些聪明的组织,你刚刚分配和管理对象的所有努力,以便事情顺利进行,好吧,只需放弃一切,从头开始”。它可以提高性能,但大多数情况下降低性能。

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

(*)我在这里谈论效率。 System.gc()永远不会打破正确的Java程序。它不会产生JVM无法获得的额外内存:在抛出OutOfMemoryError之前,JVM会完成System.gc()的工作,即使是最后的手段。

答案 5 :(得分:7)

有时候(不经常!)你真正了解过去,当前和未来的内存使用情况,然后是运行时间。这种情况不会经常发生,我会在提供正常页面的情况下从不在Web应用程序中声明。

许多年前,我在报告生成器上工作,

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

首先,因为它不是实时的并且用户期望等待报告,所以GC运行时的延迟不是问题,但我们需要以比它们请求更快的速率生成报告。

看看上面的过程大纲,很明显。

  • 我们知道,在通过电子邮件发送报告后,活动对象很少,因为下一个请求尚未开始处理。
  • 众所周知,运行垃圾收集周期的成本取决于活动对象的数量,垃圾量对GC运行的成本几乎没有影响。
  • 当队列为空时,没有什么比运行GC更好的了。

因此,当请求队列为空时,在进行GC运行时显然非常值得;这没有任何不利因素。

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

此行为是在每个安装基础上配置的,因为某些客户在每次报告极大地加强保护报告后启用强制GC。 (我希望这是因为他们的服务器上的内存很少,并且它运行了很多其他进程,所以因此很快就迫使GC减少了分页。)

我们从未检测到每次工作队列为空时无法使用强制GC运行的安装。

但是,请注意,上述情况并非常见。

答案 6 :(得分:6)

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

您不能信任" System.gc"做任何事都是令人难以置信的令人生畏的,可以很容易地引用恐惧,不确定,怀疑和#34;对语言的感觉。

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

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

让我回顾一下这个主题的论点。

  1. 效率低下:
  2. 通常,程序可能没有做任何事情,而且你知道它没有做任何事情,因为它的设计方式。例如,它可能正在使用大型等待消息框进行某种长时间的等待,并且最后它还可以添加一个收集垃圾的调用,因为运行它的时间将花费很少的时间。漫长的等待,但会避免gc在更重要的操作中起作用。

    1. 这总是一种不好的做法,并表示代码损坏。
    2. 我不同意,你拥有什么垃圾收集器并不重要。它'工作是跟踪垃圾并清理它。

      通过在使用不太重要的时候调用gc,当你的生活依赖于正在运行的特定代码但是它决定收集垃圾时,你会减少它运行的几率。

      当然,它可能不会按照您想要或期望的方式运行,但是当您确实想要调用它时,您知道什么都没发生,并且用户愿意忍受缓慢/停机。如果System.gc有效,那太好了!如果它没有,至少你试过了。除非垃圾收集器具有固有的副作用,如果垃圾收集器在手动调用时假设如何表现,并且这本身会导致不信任。

      1. 这不是一个常见的用例:
      2. 这是一个无法可靠实现的用例,但如果系统是这样设计的话。它就像制作交通信号灯,并使其成为部分/全部交通信号灯。按钮不做任何事情,它会让你质疑为什么按钮在那里开始,javascript没有垃圾收集功能所以我们不会仔细检查它。

        1. 规范说System.gc()提示GC应该运行,VM可以忽略它。
        2. 什么是"提示"?什么是"忽略"?计算机不能简单地提示或忽略某些东西,它有一些严格的行为路径,它们可能是动态的,由系统的意图引导。一个正确的答案将包括垃圾收集器在实现级别实际执行的操作,这会导致它在您请求时不执行收集。这个功能只是一个小窍门吗?我必须遇到某种条件吗?这些条件是什么?

          就目前而言,Java的GC通常看起来像是一个你不相信的怪物。你不知道它何时会来或走,你不知道它将要做什么,它将如何去做。我可以想象一些专家更好地了解他们的垃圾收集如何在每个指令的基础上工作,但绝大多数人只是希望它"只是工作",并且不得不相信一个看似不透明的算法为你工作很令人沮丧。

          阅读某些内容或被教授内容之间存在很大的差距,实际上看到它的实现,跨系统的差异,以及能够在不必查看源代码的情况下使用它。这创造了信心和掌握/理解/控制的感觉。

          总而言之,答案有一个固有的问题"这个功能可能没有做任何事情,我不会详细说明如何判断它什么时候做某事以及什么时候不做。 t以及为什么它会赢或不会,往往暗示只是反对哲学试图去做,即使它背后的意图是合理的"。

          Java GC可能会以它的方式运行,也可能没有,但要理解它,很难真正按照哪个方向去全面了解您可以信任的GC要做而不做,所以简单地不信任语言就太容易了,因为语言的目的是在哲学范围内控制行为(程序员很容易,特别是新手摔倒)从某些系统/语言行为中产生存在的危机)你能够容忍(如果你不能,你只是在不得不使用之前就会使用这种语言),而且你可以做更多的事情。控制因为没有任何理由可以控制它们本身就是有害的。

答案 7 :(得分:3)

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

答案 8 :(得分:2)

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

调用GC会给调用带来非常重要的开销,如果您在某个随机时间点执行此操作,您很可能看不到您的工作奖励。另一方面,自然触发的集合很可能会收回呼叫的成本。如果您有信息表明应该运行GC,那么您可以调用System.gc(),您应该看到好处。但是,根据我的经验,这种情况只发生在一些边缘情况下,因为你不太可能有足够的信息来理解是否以及何时应该调用System.gc()。

此处列出的一个示例是,在IDE中点击垃圾箱。如果你去参加一个会议,为什么不打它。开销不会对您产生影响,并且当您回来时可能会清理堆。在生产系统中这样做,频繁的收集呼叫将使其停止运转!即使像RMI那样的偶尔调用也会破坏性能。

答案 9 :(得分:1)

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

  

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

调用它几乎总是一个坏主意,因为自动内存管理通常比你知道什么时候更好。当内部可用内存池较低时,或者操作系统请求将一些内存交还时,它将执行此操作。

如果您知道它有帮助,可以接受调用System.gc()。我的意思是你已经在部署平台上彻底测试并测量了两种方案的行为,你可以向它展示帮助。请注意,虽然gc不容易预测 - 它可能在一次运行中有所帮助而在另一次运行中受到伤害。

答案 10 :(得分:0)

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

是的,的情况,其中System.gc()将改善(感知)性能。例如,如果应用程序的某些部分可以容忍延迟,但在其他部分则不会(上面引用的游戏示例,您希望GC在某个级别的开头发生,而不是在级别期间)。

然而,它是否会帮助或伤害(或什么都不做)高度依赖于平台(如上所述)。

所以我认为它作为最后的平台特定优化是有效的(即,如果其他性能优化是不够的)。但是你永远不应该只是因为你认为它可能有所帮助(没有特定的基准),因为它有可能不会。

答案 11 :(得分:0)

  1. 由于使用new运算符动态分配对象,所以 你可能想知道这些物体是如何被摧毁的 释放的内存供以后重新分配。

  2. 在某些语言中,例如C ++,动态分配的对象必须 通过使用删除操作符手动释放。

  3. Java采用不同的方法;它为你处理释放 自动。
  4. 实现此目的的技术称为垃圾收集。 它的工作方式如下:当不存在对对象的引用时,假定不再需要该对象,并且可以回收对象占用的内存。没有明确需要像在C ++中那样销毁对象。
  5. 垃圾收集只会偶尔发生(如果有的话) 执行你的程序。
  6. 不会因为存在一个或多个对象而发生 不再使用。
  7. 此外,将采用不同的Java运行时实现 不同的垃圾收集方法,但在大多数情况下,你 在编写程序时不应该考虑它。

答案 12 :(得分:0)

我将要写的一些内容只是对其他答案中已经写过的内容的总结,有些是新的。

问题“为什么调用 System.gc() 是不好的做法?”不计算。它假设这是不好的做法,而事实并非如此。这在很大程度上取决于您要实现的目标。

那里的绝大多数程序员不需要 System.gc(),而且在绝大多数用例中它永远不会对他们做任何有用的事情。因此,对于大多数人来说,称其为不好的做法,因为它不会做他们认为会做的任何事情,只会增加开销。

但是,在少数情况下调用 System.gc() 实际上是有益的:

  1. 当您绝对确定自己有一些 CPU 时间可以使用现在,并且您希望提高稍后运行的代码的吞吐量时。例如,Web 服务器发现此时没有待处理的 Web 请求,可以立即发起垃圾收集,以减少以后处理一连串 Web 请求时需要进行垃圾收集的机会。 (当然,如果 Web 请求在收集期间到达,这可能会造成伤害,但 Web 服务器可能对此很聪明,并在请求进来时放弃收集。)桌面 GUI 是另一个示例:在空闲事件上(或者更广泛地说,在一段时间不活动,)你可以给 JVM 一个提示,如果它有任何垃圾收集要做,现在总比以后好。

  2. 当您想要检测内存泄漏时。这通常与仅调试模式的终结器或 Java 9 以后的 java.lang.ref.Cleaner 类结合使用。这个想法是通过强制垃圾收集现在,从而发现内存泄漏现在而不是将来的某个随机时间点,你可以尽快检测到内存泄漏尽可能在它们发生之后,因此可以更好地准确判断哪一段代码泄漏了内存以及原因。 (顺便说一句,这也是终结器或 Cleaner 的合法用例之一,或者可能是唯一的合法用例。使用终结器回收非托管资源的做法是有缺陷的,尽管非常普遍甚至官方推荐,因为它是不确定的。有关此主题的更多信息,请阅读:https://blog.michael.gr/2021/01/object-lifetime-awareness.html)

  3. 当您测量代码的性能时,(基准测试)是为了减少/最小化在基准测试期间发生垃圾收集的机会,或者为了保证由于垃圾收集在运行期间遭受的任何开销基准测试是由于基准测试下的代码生成的垃圾,而不是无关代码。一个好的基准测试总是从尽可能彻底的垃圾收集开始。

  4. 当你测量代码的内存消耗时,为了确定一段代码产生了多少垃圾。其思想是执行一次完全垃圾收集,以从干净状态开始,运行正在测量的代码,获取堆大小,然后再进行一次完全垃圾收集,再次获取堆大小,并取差值。 (顺便说一句,在运行被测量的代码时暂时抑制垃圾收集的能力在这里很有用,可惜 JVM 不支持。这是令人遗憾的。)

请注意,在上述用例中,只有一个是在生产场景中;其余用于测试/诊断场景。

这意味着 System.gc() 在某些情况下非常有用,这反过来意味着它“只是一个提示”是有问题的。

(只要 JVM 不提供某些确定性和有保证的垃圾收集控制方法,JVM 在这方面就被破坏了。)

您可以通过以下方式将 System.gc() 转化为更少的提示:

private static void runGarbageCollection()
{
    for( WeakReference<Object> ref = new WeakReference<>( new Object() ); ; )
    {
        System.gc(); //optional
        Runtime.getRuntime().runFinalization(); //optional
        if( ref.get() == null )
            break;
        Thread.yield();
    }
}

这仍然不能保证你会得到一个完整的 GC,但它会更接近。具体来说,即使使用了 -XX:DisableExplicitGC VM 选项,它也会为您提供一定数量的垃圾收集。 (因此,它真正使用 System.gc() 作为提示;它不依赖它。)

答案 13 :(得分:-1)

我的2美分:我在一个活动中加载了一些AnimationDrawable并播放它们。我加载,播放,然后将imageview背景设置为null,一次。如果我退出活动然后再快速回来,经过3到4次后,内存增长太多,直到我出现内存不足异常。

通过在将imageview background设置为null后显式调用垃圾收集器,我在Eclipse logcat上看到内存保持足够自由 - 在我的情况下gc实际上是运行的 - 我不会让应用程序停止工作了。

很明显,系统可能会决定推迟gc的执行,但如果你知道gc的工作方式或多或少,你就可以信任像我这样的情况,它会尽快被调用,因为系统会注意到内存使用增长和应用它将要求更多的系统。我认为它就像c ++ std库容器一样:你得到一些起始内存,每次它都不够,它会加倍。

说如果你需要调用它,那是因为代码坏了或坏是一种不合理的教条回答我的方式:特别是如果你可以使用像C ++这样的全手动内存管理语言进行编程,你必须面对极限相反,使用像java这样的语言的移动设备上的资源,没有机会手动释放内存,你很快就可以想到许多需要明确调用gc的情况,特别是在你有跟踪gc而不是引用计数的情况下,你的代码很干净,做得很好。