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

在用System.gc() 回答一个关于如何在Java中强制释放对象的问题(这个人正在清除一个1.5GB的HashMap System.gc() ,我被告知手动调用System.gc()是一个坏习惯,但是这些注释并不是完全有说服力。 另外,似乎没有人敢鼓掌,也不赞成我的回答。

我被告知在那里这是不好的做法,但后来我也被告知,垃圾收集器的运行不会系统地停止世界了,它也可以有效地被JVM用作暗示,所以我是种在损失。

我明白,当需要回收内存时,JVM通常比你更清楚。 我也明白,担心几千字节的数据是愚蠢的。 我也明白,即使是兆字节的数据也不是几年前的情况。 但是,仍然是1.5千兆字节? 而且你知道有1.5 GB的数据存储在内存中。 这不像是在黑暗中的一枪。 System.gc()系统性的坏,还是有一些点可以吗?

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

  • 为什么调用System.gc()是或不是不好的做法? 在特定的实现中,它仅仅是JVM的一个暗示,还是总是一个完整的收集周期? 真的有垃圾收集器的实现,可以做他们的工作,而不是停止世界? 请对我的回答中的人们在评论中提出的各种主张发表一些看法。
  • 门槛在哪里? 调用System.gc() 不是一个好主意,还是有时候可以接受? 如果是的话,那是什么时候?

每个人总是说避免System.gc()是,它是一个非常好的指标,从根本上破坏代码 。 依赖于正确性的任何代码肯定是坏的; 任何依靠它来performance的东西都很可能被打破。

你不知道你正在运行什么types的垃圾回收器。 肯定有一些不会像你所说的那样“停止世界” ,但是有些JVM不是那么聪明或者有各种各样的原因(也许他们在打电话?)不这样做。 你不知道该怎么做。

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

“你不知道会做什么”,“你不知道它是否会有帮助”,“你不应该这样说”,这就是为什么人们如此强烈地说:你不应该叫它。 我认为这是一个“如果你需要问你是否应该使用这个,你不应该”


编辑来解决从另一个线程的几个问题:

在阅读你所链接的主题后,还有一些我想指出的内容。 首先,有人build议调用gc()可能会将内存返回给系统。 这当然不一定是真的 – Java堆本身增长独立于Java分配。

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

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

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

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

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

然而,调用System.gc()不好的做法的实用性原因是效率低下。 而在最坏的情况下,这是非常低效的 ! 让我解释。

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

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

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

第一个电话会(我们预测)做(live * W1 + garbage * W2)工作,摆脱优秀的垃圾。

第二个电话会做(live* W1 + 0 * W2)工作和收回什么。 换句话说,我们已经完成了(live * W1)工作,并没有取得任何成果

我们可以将收集器的效率build模为收集垃圾单位所需的工作量; 即efficiency = (live * W1 + garbage * W2) / garbage 。 所以为了使GC尽可能高效,我们需要在运行GC时最大化 garbage的价值; 即等到堆满了。 (并且尽可能地把堆做得很大,但这是一个单独的话题。)

如果应用程序不会干扰(通过调用System.gc() ),则GC将在运行之前等待堆满,从而有效地收集垃圾1 。 但是,如果应用程序强制GC运行,那么堆中的可能性就会不充分,结果就是垃圾被无效收集。 而应用程序越频繁地使用GC,GC的效率越低。

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


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

2 – 我也排除了专门使用引用计数的内存pipe理器,但目前的Java实现没有使用这种方法…是有原因的。

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

  1. 你有很多的对象是无法访问,可能没有被gc'ed。 和
  2. 你认为用户在这一点上可以放慢一点

调用System.gc()并没有什么坏处。 我把它看作是c / c ++ inline关键字。 这对开发人员来说只是暗示gc已经决定时间/性能不像以往那样重要,而且其中的一部分可以用来回收内存。

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

有一次,我会强制收集:当试图找出是一个特定的对象泄漏(无论是本地代码或大型的,复杂的callback交互,哦,以及任何UI组件,看看Matlab)。 在生产代码。

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

(下面的注释适用于运行在CMS收集器的Linux上的Hotspot,在那里我确信System.gc()实际上总是调用一个完整的垃圾收集)。

  1. 在启动应用程序的初始工作之后,您可能是一个可怕的内存使用状态。 你一半的年龄可能充满了垃圾,这意味着你离你的第一个CMS更近了。 在重要的应用程序中,调用System.gc()将您的堆“重置”到实时数据的开始状态并不是一个坏主意。

  2. 与#1一样,如果您密切监视堆的使用情况,则希望准确读取基准内存使用情况。 如果应用程序的正常运行时间的前2分钟都是初始化,那么您的数据将会被搞乱,除非您强迫(呃…“build议”)完整的gc。

  3. 您可能有一个应用程序,旨在永远不会促进任何东西到终身的一代,而它正在运行。 但是,也许你需要预先初始化一些不那么庞大的数据,以便自动地转移到终身的一代。 除非在所有设置完成后调用System.gc(),否则您的数据可能会坐在新一代中,直到升级。 突然之间,您的超级低延迟低GC应用程序在正常运行期间被用来促进这些对象的巨大(相对来说,当然是)延迟惩罚。

  4. 在生产应用程序中使用System.gc调用来validation是否存在内存泄漏有时很有用。 如果您知道时间X处的实时数据集应该与时间Y处的一组实时数据存在一定比例,那么调用System.gc()一个时间X和时间Y并比较内存使用情况可能会很有用。

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

调用System.gc()就像踢GC一样。 它的意思是:“所有那些经过精心调整的参数,那些聪明的组织,你刚刚投入到分配和pipe理对象中的所有努力,以便事情顺利进行,就这么一干二净,从头开始”。 它可能会提高性能,但大部分时间只会降低性能。

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

(*)我在这里谈论效率。 System.gc()永远不会破坏一个正确的Java程序。 它不会唤起JVM无法获得的额外内存:在抛出OutOfMemoryError之前,JVM执行System.gc()的工作,即使作为最后的手段。

这是一个非常麻烦的问题,我感到许多人反对Java,尽pipe它是一种语言有多么有用。

事实上,你不能相信“System.gc”做任何事情是令人难以置信的艰巨,可以轻易地引起对语言的“恐惧,不确定,怀疑”的感觉。

在很多情况下,在重要事件发生之前处理内存溢出是有意的,这会导致用户认为你的程序devise不好/没有反应。

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

让我回顾一下这个线程的论点。

  1. 这是低效的:

通常情况下,程序可能没有做任何事情,你知道它没有做任何事情,因为它的devise方式。 例如,它可能会用一个大的等待消息框进行某种长时间的等待,并且最后还可能添加一个调用来收集垃圾,因为运行它的时间将占用非常小的时间漫长的等待,但是会避免gc在更重要的操作中出现。

  1. 这总是一个不好的做法,并指出破码。

我不同意,不pipe你有什么垃圾收集器。 它的工作是追踪垃圾并清理垃圾。

通过在使用不太重要的时间调用gc,当您的生活依赖于正在运行的特定代码,而是决定收集垃圾时,可以减less运行的几率。

当然,这可能不像你想要的那样行事,但是当你想要调用它的时候,你什么都不知道,用户愿意忍受缓慢/停机时间。 如果System.gc工作,太棒了! 如果没有,至less你试过。 除非垃圾收集器有固有的副作用,如果垃圾收集器如果手动调用,会产生非常意外的结果,而这本身就会引起不信任。

  1. 这不是一个常见的用例:

这是一个不能可靠实现的用例,但是如果系统是这样devise的话。 这就像制作一个交通灯,使一些/所有的交通灯的button不做任何事情,这让你质疑为什么button在那里开始,JavaScript没有垃圾收集function,所以我们不不要仔细研究它。

  1. 规范说System.gc()是GC应该运行的提示,虚拟机可以自由地忽略它。

什么是“提示”? 什么是“忽略”? 一台计算机不能简单地提示或忽略某些事情,它有严格的行为path,它可能是dynamic的,受制于系统的意图。 一个正确的答案将包括垃圾收集器在实现级别实际上正在做什么,导致它在您请求时不执行收集。 这个function简直就是一个nop吗? 我遇到过什么样的情况? 这些条件是什么?

就目前而言,Java的GC往往像是一个你不信任的怪物。 你不知道什么时候会出现,你不知道该怎么做,怎么做。 我可以想象一些专家更好地了解他们的垃圾收集如何在每个指令的基础上工作,但绝大多数人只是希望它“只是工作”,不得不相信一个不透明的看起来algorithm为你做的工作是令人沮丧的。

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

总而言之,答案有一个固有的问题:“这个function可能不会做任何事情,我不会详细地告诉他们什么时候做什么事情,什么时候做什么事情,为什么不做或不会做什么,往往意味着试图这样做是完全违背哲学的,即使它背后的意图是合理的“。

Java GC的行为方式可能没有问题,或者它可能不行,但要理解它,真正难以真正遵循向哪个方向去全面了解可以信任GC的内容,不要这样做,所以太容易了,简单的就不信任语言,因为一门语言的目的就是控制了哲学范围内的行为(程序员,特别是新手很容易从某些系统/语言行为中陷入存在危机),你是有能力容忍的(如果你不能,你只是不会使用这种语言,直到你必须),更多的事情,你无法控制,因为你无法控制它们,没有任何已知的理由本质上是有害的。

首先,规范和现实是有区别的。 规范说,System.gc()是GC应该运行的提示,虚拟机可以自由地忽略它。 实际情况是,VM 永远不会忽略对System.gc()的调用。

调用GC会带来不小的开销,如果在某个随机时间点执行此操作,很可能会看不到您的努力。 另一方面,自然触发的收集很有可能收回通话费用。 如果你有信息表明GC应该运行,你可以调用System.gc(),你应该看到好处。 但是,根据我的经验,这种情况只发生在几个边缘情况下,因为很less有足够的信息来了解System.gc()是否应该被调用。

这里列出了一个例子,在你的IDE中点击垃圾箱。 如果你正在开会,为什么不去碰它呢? 开销不会影响你,当你回来的时候堆可能会被清理。 在生产系统中这样做,并经常打电话来收集将使它停下来! 即使是像RMI这样偶尔的电话也会对性能造成干扰。

有时( 不经常! )你确实知道更多关于过去,现在和将来的内存使用情况,然后运行时间。 这种情况不会经常发生,而且我会在网页应用程序中宣称从不在正常页面正在服务的情况下。

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

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

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

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

  • 我们知道,在报告发出后,只有极less数活体对象,因为下一个请求还没有开始处理。
  • 众所周知,运行垃圾收集周期的成本取决于活动对象的数量,垃圾的数量对GC运行的成本几乎没有影响。
  • 当队列空时,没有什么比这更好的了,然后运行GC。

因此,当请求队列为空时,显然在进行GC运行时非常值得; 这没有坏处。

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

此行为是在每个安装基础上configuration的,对于某些客户在每次报告后启用强制GC,都大大加快了报告的保护。 (我期望这是由于他们的服务器内存不足,并且运行了很多其他进程,所以很快就会迫使GC减less分页。)

我们从来没有发现一个没有得到好处的安装是在每次工作队列为空时强制执行GC。

但是,要说清楚,以上情况并非常见。

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

调用gc方法表明,Java虚拟机花费大量的工作来回收未使用的对象

调用它几乎总是一个坏主意,因为自动内存pipe理通常比你什么时候知道gc。 当它的内部空闲内存池很低时,或者如果操作系统请求回传一些内存,它会这样做。

如果你知道它有帮助,可以调用System.gc()。 我的意思是,您已经在部署平台上彻底testing和测量了两种scheme的行为,并且可以显示它的帮助。 请注意,gc是不容易预测的 – 它可能会帮助一次运行而伤害另一次。

也许我写了蹩脚的代码,但是我已经认识到,点击eclipse和netbeans IDEs上的垃圾桶图标是一个“好习惯”。

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

是的,有些情况下System.gc()会提高(感知)性能。 举个例子,如果在你的应用的某些部分,延迟是可以忍受的,但是在其他部分(比如上面提到的游戏例子,你希望GC在一个关卡开始的时候,而不是关卡发生的时间)。

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

所以我认为这是作为最后一个平台特定的优化(即如果其他性能优化是不够的)有效。 但是你不应该仅仅因为你相信这可能会有帮助(没有具体的基准),因为机会是不会的。

  1. 由于使用新运算符来dynamic分配对象,
    你可能想知道这些物体是如何被破坏的
    内存释放以供以后重新分配。

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

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

我的2美分:我在一个活动中加载一些AnimationDrawables并播放它们。 我加载,播放,然后将imageview背景设置为null,一次。 如果我退出活动,然后很快再次回来,3或4次内存的增长太多,直到我得到一个内存不足的例外。

通过在将imageview背景设置为null之后显式调用垃圾回收器,我在Eclipse logcat上看到内存保持足够的释放 – 在我的情况下,gc实际上是运行的 – 并且我不再让应用程序停止工作。

很明显,系统可能决定推迟gc的执行,但是如果你知道某个gc是如何工作的,你可以相信像我这样的情况会尽快调用,因为系统会注意到使用的内存越来越大,应用程序即将要求更多的系统。 我认为它的工作原理就像c ++ std库容器:你得到一些开始的内存,每次它不够,它加倍。

说如果你需要称之为破坏或坏的代码是一个不合理的教条式的回答我的方式:特别是如果你可以用像C ++这样的全部手动内存pipe理的语言进行编程,并且你必须面对资源的限制移动设备与Java语言,而不是手动释放内存的机会,你很快就可以想到很多情况下,有必要显式调用GC,特别是你有一个跟踪gc,而不是一个引用计数一个,你的代码干净,干得好。