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

Why is it bad practice to call System.gc()?

在回答一个关于如何强制Java中的免费对象(这个家伙正在清除1.5 GB的HASMAP)的问题时,EDCOX1(0),我被告知,手动调用EDCOX1×0是不好的做法,但是这些评论并不完全令人信服。此外,似乎没有人敢反对,也没有人反对我的回答。

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

我确实理解,当需要回收内存时,JVM通常比您更了解。我也明白担心几千字节的数据是愚蠢的。我也明白,即使是兆字节的数据也不是几年前的情况。但仍然是1.5千兆字节?你知道内存中有1.5GB的数据,这不像是在黑暗中拍摄。System.gc()是否系统性地不好,或者是否存在某种程度的问题?

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

  • 为什么叫System.gc()是不好的做法?在某些实现中,它仅仅是对JVM的一个提示,还是总是一个完整的收集周期?有没有真正的垃圾收集器实现可以在不停止世界的情况下完成它们的工作?请对人们在对我的回答的评论中所作的各种断言给予一些解释。
  • 门槛在哪里?打电话给System.gc()从来不是个好主意,还是有时候可以接受?如果是的话,那是什么时候?


每个人总是说要避免使用System.gc(),原因是它是一个很好的基本代码损坏指示器。任何依赖它来保证正确性的代码都肯定会被破坏;任何依赖它来提高性能的代码都很可能被破坏。

你不知道你在用哪种垃圾收集器。当然,有些合资公司并不像你所说的那样"阻止世界",但有些合资公司并不是那么聪明,或者出于各种原因(也许他们在打电话?)不要这样做。你不知道它会做什么。

而且,它不能保证做任何事情。JVM可能会完全忽略您的请求。

"你不知道它会做什么"、"你甚至不知道它是否会有帮助"和"你无论如何都不需要称它"的组合,是为什么人们如此强硬地说你一般不应该称它。我认为这是一个"如果你需要问你是否应该使用这个,你不应该"

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

在阅读了你链接的那条线之后,我想指出更多的事情。首先,有人建议调用gc()可能会将内存返回到系统。这当然不一定是真的——Java堆本身独立于Java分配而增长。

与中一样,JVM将保存内存(许多10兆字节)并根据需要增加堆。即使当您释放Java对象时,它也不一定会将内存返回到系统中;它完全可以保存到分配的内存中,以用于将来的Java分配。

为了证明System.gc()可能什么都不做,请查看:

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

特别是有一个-xx:disableexplicitgc-vm选项。


已经有人解释说,调用system.gc()可能什么都不做,任何"需要"垃圾收集器运行的代码都会被破坏。

然而,所谓system.gc()是不好的做法的实际原因是它效率低下。在最坏的情况下,它效率极低!让我解释一下。

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

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

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

第一个调用(我们预测)将执行(live * W1 + garbage * W2)工作,并清除未处理的垃圾。

第二个电话将完成(live* W1 + 0 * W2)的工作,不回收任何东西。换言之,我们已经完成了(live * W1)的工作,但毫无成就。

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

如果应用程序不干扰(通过调用system.gc(),GC将等待堆满后再运行,从而有效地收集garbage1。但是,如果应用程序强制GC运行,堆可能不会满,结果是垃圾收集效率低下。应用程序越频繁地强制GC,GC的效率就越低。

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

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

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


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

  • 您有许多无法访问的对象,可能还未被GC'ED。
  • 你认为用户可以在这一点上忍受一个小的减速。
  • 调用System.gc()没有任何危害。我看它像C/C+EDCOX1〉0 }关键字。这只是GC的一个提示,您,开发人员,已经决定了时间/性能并不像通常那样重要,并且可以使用其中的一些来回收内存。

    建议不要依赖它做任何事情是正确的。不要依赖于它的工作,但是暗示现在是一个可以接受的收集时间是完全可以的。我宁愿把时间浪费在代码中不重要的地方(加载屏幕),也不愿浪费在用户主动与程序交互的时候(比如在游戏中)。

    有一次我将强制收集:当试图找出某个特定对象是否泄漏时(本机代码或大型、复杂的回调交互)。哦,还有任何一个可以浏览matlab的用户界面组件。)这在生产代码中不应该使用。


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

    (以下评论适用于使用CMS收集器在Linux上运行的Hotspot,我相信System.gc()实际上总是调用完整的垃圾收集)。

  • 在启动应用程序的初始工作之后,可能是内存使用的糟糕状态。你的下一代可能有一半都是垃圾,这意味着你离你的第一代CMS更近了。在那些重要的应用程序中,调用System.gc()将堆"重置"为活动数据的起始状态并不是一个坏主意。

  • 沿着与1相同的行,如果密切监视堆的使用情况,则需要准确读取基线内存使用情况。如果应用程序正常运行的前2分钟都是初始化时间,那么除非强制执行(ahem…),否则您的数据将被破坏。"建议")提前完整的GC。

  • 您可能有一个应用程序,其设计目的是在运行时永远不会将任何内容提升到终身代。但是,也许您需要预先初始化一些数据,这些数据不会太大,因此可以自动转移到保留期的一代。除非在设置完所有内容后调用System.gc(),否则您的数据可能会保存在新一代中,直到升级。突然间,您的超级duper低延迟、低GC应用程序受到了巨大(当然,相对而言)延迟惩罚,因为在正常操作期间提升了这些对象。

  • 在生产应用程序中使用system.gc调用来验证内存泄漏的存在有时很有用。如果您知道时间x的一组实时数据应该与时间y的一组实时数据以一定的比例存在,那么调用System.gc()时间x和时间y并比较内存使用情况可能会很有用。


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

    System.gc()就像踢GC。它的意思是:"所有那些经过仔细调整的参数,那些聪明的组织,所有你在分配和管理对象上所做的努力,使事情顺利进行,好吧,放弃所有的工作,从头开始。"它可以提高性能,但大多数时候它只是降低性能。

    要可靠地使用System.gc()(*),您需要了解GC如何在所有细节中运行。如果您使用来自另一个供应商的JVM,或者来自同一个供应商的下一个版本,或者同一个JVM,但是命令行选项略有不同,那么这些细节会发生很大的变化。因此,除非您想解决一个特定的问题,在这个问题中控制所有这些参数,否则这几乎不是一个好主意。因此,"坏习惯"的概念:这不是禁止的,方法是存在的,但很少有回报。

    (*)我说的是效率。EDCOX1 0将永远不会破坏正确的Java程序。它也不会使人联想到JVM不可能获得的额外内存:在抛出一个OutOfMemoryError之前,JVM会完成System.gc()的工作,即使是作为最后手段。


    有时(不经常!)您确实比运行时更了解过去、现在和将来的内存使用情况。这种情况不会经常发生,我也不会在正常的网页服务期间在Web应用程序中声明。

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

    • 只有一根线
    • 从队列中读取"报告请求"
    • 已从数据库加载报表所需的数据
    • 生成报告并通过电子邮件发送出去。
    • 一次又一次,在没有未完成的请求时沉睡。
    • 它没有在报表之间重用任何数据,也没有进行任何兑现。

    首先,由于它不是实时的,而且用户希望等待报告,所以在GC运行期间的延迟不是问题,但我们需要以比请求更快的速度生成报告。

    从以上过程的概要来看,很明显。

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

    因此,在请求队列为空时执行GC运行显然是很有价值的;这样做没有任何不利之处。

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

    这种行为是在每个安装的基础上配置的,对于某些客户来说,在每个报告之后启用强制GC大大加快了对报告的保护。(我预计这是由于服务器内存不足,并且它运行了许多其他进程,因此强制GC缩短了分页时间。)

    我们从未检测到不利于的安装是每次工作队列为空时强制GC运行。

    但是,让我们澄清一下,上述情况并不常见。


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

    事实上,你不能信任"system.gc"去做任何事情,这是令人难以置信的可怕,而且很容易让语言产生"恐惧、不确定性、怀疑"的感觉。好的。

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

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

    让我回顾一下这个线程的参数。好的。

  • 效率低下:
  • 通常,程序可能不会做任何事情,而且您知道它不会做任何事情,因为它的设计方式。例如,它可能正在用一个大的等待消息框进行某种长时间等待,最后它还可能添加一个调用来收集垃圾,因为运行垃圾的时间只占长时间等待时间的很小一部分,但会避免GC在更重要的操作中出错。好的。

  • 这始终是一个错误的实践,并表明代码被破坏。
  • 我不同意,你有什么垃圾收集者并不重要。它的工作是跟踪垃圾并清理它。好的。

    通过在使用不那么重要的时候调用GC,当您的生命依赖于正在运行的特定代码,而它决定收集垃圾时,您可以降低运行GC的几率。好的。

    当然,它可能不会按照您希望或期望的方式运行,但是当您确实想调用它时,您知道什么都没有发生,并且用户愿意容忍缓慢/停机。如果system.gc工作正常,太好了!如果没有,至少你试过了。除非垃圾收集器有一些固有的副作用,这些副作用会对垃圾收集器在手动调用时的行为产生意想不到的可怕影响,而且这本身会导致不信任。好的。

  • 它不是常见的用例:
  • 它是一个不能可靠实现的用例,但是如果系统是这样设计的,它也可能实现。这就像制造一个红绿灯,让部分/所有红绿灯的按钮都不做任何事情,这让你质疑为什么按钮在那里开始,javascript没有垃圾收集功能,所以我们没有仔细检查它。好的。

  • 规范说system.gc()是gc应该运行的提示,vm可以随意忽略它。
  • 什么是"提示"?什么是"忽略"?计算机不能简单地获取提示或忽略某些内容,它所采用的严格行为路径可能是由系统意图引导的动态路径。正确的答案将包括垃圾收集器在实现级别实际执行的操作,这会导致它在您请求时不执行收集。这个功能仅仅是一个nop吗?有什么条件需要我满足吗?这些条件是什么?好的。

    事实上,Java的GC看起来像是一个你不信任的怪物。你不知道它什么时候会来,什么时候去,你不知道它会做什么,它会怎么做。我可以想象一些专家更好地了解他们的垃圾收集是如何按指令进行的,但是绝大多数人只是希望它"正常工作",并且必须信任一个看起来不透明的算法来为您工作是令人沮丧的。好的。

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

    总而言之,答案有一个固有的问题,"这个特性可能不会做任何事情,我也不会详细说明如何判断它何时做了什么,何时不做,以及为什么不做或将要做,通常意味着尝试做这件事是违反哲学的,即使它背后的意图是合理的"。好的。

    JavaGGC可以按照它的方式行事,或者它可能不是,但是要理解它,很难真正遵循哪一个方向去全面了解你可以相信GC做什么和不做什么,所以很容易就不信任语言,因为语言的目的是控制行为。从哲学的角度来说(程序员,尤其是新手,很容易因为某些系统/语言行为而陷入生存危机),你能够容忍(如果你不能,那么在不得不使用之前,你就不会使用语言),还有更多你无法控制的东西,因为不知道为什么你不能控制它们是内在有害的。好的。好啊。


    也许我写了一些蹩脚的代码,但我逐渐意识到点击Eclipse和NetBeans IDES上的垃圾桶图标是一种"好的实践"。


    首先,规范和现实之间存在差异。规范说system.gc()是gc应该运行的提示,vm可以随意忽略它。事实是,虚拟机永远不会忽略对System.gc()的调用。

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

    这里列出了一个例子,点击您的IDE中的垃圾桶。如果你要去开会,为什么不去参加呢?开销不会影响您,当您返回时堆可能会被清理掉。在生产系统中执行此操作,频繁呼叫对方将使其停止工作!即使是像RMI这样的偶尔调用也可能会破坏性能。


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

    Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects

    调用它几乎总是一个坏主意,因为自动内存管理通常比您更了解何时使用GC。当其内部可用内存池较低时,或者如果操作系统请求返回一些内存,它就会这样做。

    如果您知道System.gc()有帮助,那么调用它可能是可以接受的。我的意思是,您已经在部署平台上对这两个场景的行为进行了全面的测试和测量,并且可以向您展示它的帮助。尽管GC不容易预测,但要注意它可能在一次运行中有所帮助,在另一次运行中也会受到伤害。


  • 因为对象是通过使用new操作符动态分配的,你可能想知道这些物体是如何被摧毁的释放内存以供以后重新分配。

  • 在某些语言,如C++中,动态分配的对象必须通过使用删除运算符手动释放。

  • Java采用了不同的方法,它为您处理解除分配。自动地。
  • 实现这一点的技术称为垃圾收集。它的工作原理是这样的:当不存在对对象的引用时,假定不再需要该对象,并且可以回收该对象占用的内存。在C++中没有明确的对象销毁需求。
  • 垃圾收集仅在执行你的程序。
  • 它不会仅仅因为存在一个或多个对象不再使用。
  • 此外,不同的Java运行时实现将采用垃圾收集的方法多种多样,但在大多数情况下,在编写程序时不必考虑它。

  • 根据我的经验,使用system.gc()实际上是一种平台特定的优化形式(其中"平台"是硬件体系结构、操作系统、JVM版本以及可能更多的运行时参数(如RAM可用),因为在特定平台上,它的行为虽然可以大致预测,但在不同的平台上,它可以(并且将)发生很大的变化。TFEMS。

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

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

    因此,我认为它是有效的最后手段平台特定的优化(即,如果其他性能优化不够)。但你永远不应该仅仅因为你相信它可能会有所帮助(没有具体的基准),因为很可能它不会。


    我的2分:我在一个活动中加载一些动画绘图,然后播放它们。我加载、播放,然后将ImageView背景设置为空,一次一个。如果我退出活动,然后很快又回来,经过3到4次后,占用的内存会增长太多,直到出现内存不足异常。

    通过在将ImageView背景设置为空后显式调用垃圾收集器,我在Eclipse Logcat上看到内存有足够的空闲空间——在我的例子中,gc实际上是在运行的——并且我不会让应用程序停止工作。

    很明显,系统可能会决定推迟GC的执行,但是如果你或多或少知道GC的工作原理,你可以信任像我这样的情况,它会尽快被调用,因为系统会注意到内存在增长,应用程序会向系统要求更多。我认为它像C++ STD库容器一样工作:你得到一些启动内存,每次都不够,它是双倍的。

    如果你需要调用它,它是由于坏代码或坏代码是一种不合理的教条方式回答我:特别是如果你可以用一种语言编程,如C++的全手动内存管理,你必须面对像Java这样一种语言的移动设备上的资源限制,而没有机会手动释放内存。Ickly可以考虑许多情况,在这些情况下显式地调用GC是必要的,特别是当您有一个跟踪GC而不是引用计数GC时,您的代码是干净的,并且完成得很好。