智能垃圾收集?

3

您可以通过调用System.gc()在Java中进行垃圾回收,但有时会导致应用程序“停滞”。像这样进行垃圾回收并避免停滞是否是一个不好的主意:

new Thread(new Runnable() {
   public void run() { 
      System.gc(); 
   }
}).start();

或者这会导致更多的问题吗?

1
那只会调用一次System.gc()...而且你不认为Java已经在另一个线程中进行了垃圾回收吗? - Marcelo
4
标题中的问题可以回答为:“是的,垃圾收集器很聪明。实际上,它比你更聪明,并且是自主的。” ;) 你认为为什么要首先调用 System.gc()?你了解是什么导致了这些停顿(顺便说一下,并非所有GC都有这种停顿)?如果是,稍微思考一下就能回答你的问题。如果不了解,你应该在尝试超越它们之前,先提高对GC和现代JVM的理解。 - user395760
如果你真正想要调整GC以避免停顿而不是释放内存,你可能还想尝试使用Oracle JVM中的其他GC,并考虑使用服务器VM。这可以帮助你。如果可以,请提供更多关于你的用例的详细信息。 - haylem
6个回答

15

首要的事情:不要显式调用GC

除非你有一个非常好的理由。即使你认为有理由这样做,你可能也没有。

GC知道自己在做什么(大多数情况下...)

如果你继续阅读,我假设你有一个真正好的(尽管可能扭曲的)理由来试图捣鬼GC,即使它在确定何时收集内存方面比你更聪明是非常有可能的。此外,请记住,显式地调用它会让它感到困惑并破坏它的启发式算法,因此它变得比以前更不聪明了,所有这些都是因为你试图超越它。

GC并不总是关心你说的话

如果你有非常好的理由这样做,或者在你真正想确保在最佳内存状态下开始进行密集代码段的情况下,你需要知道这可能行不通:对System.gc()的调用不能保证垃圾回收将发生,如在其Javadoc中提到我的强调

调用gc方法建议Java虚拟机花费努力回收未使用的对象。

其他建议

查找和消除(不好的)显式GC

  • 打开-XX:+DisableExplicitGC(如果您的JVM支持它),以防止那些疯狂的调用对系统造成任何伤害(感谢评论区中的Fredrik)
  • 使用您喜欢的IDE或grep查找对System.gc()及其等效方法的调用,并摆脱它们。

寻找其他方法

请参阅Grooveek's answer获取其他有用的建议(例如使用WeakReference)。

尝试其他GC并为您的应用程序微调虚拟机

根据您的用例,可能尝试其他GC实现会有所帮助:CMC、G1、ParallelGC等。如果您想避免“停顿”,自Java SE 6更新以来,我一直在最新的Java 7版本中使用G1取得了非常好的结果,长时间运行密集型企业应用程序。

只需注意,JVM调优是一门非常复杂的艺术。

进一步阅读

您可以翻阅这些内容以获取更多细节:

* 谨慎使用:有时不是完全最新的,没有记录所有内容,并列出了许多实验性功能或仅限于HotSpot的功能。


2
有人会认为,如果你要将某些内容全部大写,你会进行拼写检查。我想并不是这样。 - Stefan Kendall
2
@Stefan Kendall:人们会认为语言专家会给非英语母语者编辑答案的时间。但是感谢您指出我的错误,无论多么友好 :) - haylem
也许你应该添加“完成后,那个。确保还要将-XX:+ DisableExplicitGC添加到JVM选项中”。 - Fredrik
2
@Marcelo Hernández Rishmawy:是的,我看到了那个并且想“哎呀”……在你的评论出现之前我已经纠正了它 :) 公平地说,Stefan的评论是完全正确的,因为在提交之前我应该进行拼写检查(我甚至会给他点赞),但是我通常喜欢以非常短的间隔编辑我的答案,这样我就可以通过小的迭代来改进它。 - haylem
(以《飞翔的蒙特·派森》圣杯守卫的语气)嗯,但是啊...嗯,但是啊...如果我们真的想调用垃圾收集器呢? ;) - riwalk
@Stargazer712:如果你真的必须这样做,可以通过JVMTI实现:http://download.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#ForceGarbageCollection。但是你真的不需要这样做 :) (好吧,也许只有在调试或分析性能时才需要) - haylem

3

是的,大多数时候调用System.gc()是一个非常糟糕的想法。虽然也有例外情况,但它们很少见,主要还是花时间确保你不做会影响垃圾回收性能的事情,并学习并确保你理解如何进行垃圾回收,而不是通过显式调用System.gc()来自己处理。


好的,谢谢您回答我的问题而不是表现得像我什么都不懂。在我的应用程序中,我设置它在停止非常处理器密集型并可能持续几个小时之后立即进行垃圾收集,但当然您比我更了解这个(这就是我问这个问题的原因)。感谢您的回复,我会去多看些资料的。 - anon
@anon 这个博客上有几篇非常好的博客文章,我认为它们是链接在这里的:http://chaoticjava.com/posts/how-does-garbage-collection-work/ - Fredrik

2
我无法超越@haylem的清晰和力量,很抱歉。但是让我补充一下,在Java中有很多(更好的)管理内存的方法。例如,有WeakReference和处理它们的集合,如WeakHashMap。这些是确定性的内存处理方式,尽管根据javadoc,显式调用GC不是。
Calling the gc method suggests that the Java Virtual Machine expend effort toward 
recycling unused objects in order to make the memory they currently occupy available 
for quick reuse. When control returns from the method call, the Java Virtual Machine 
has made a best effort to reclaim space from all discarded objects.

其实,也许你并没有完全击败它,但这是一个非常好的补充,我应该提到WeakReferences。非常好的观点。 - haylem

0
除了迄今为止已经说过的内容之外,整个想法非常有缺陷,因为您遗漏了一个重要细节:
在进行完整的GC时,应用程序将停止运行!(至少目前在现代Hotspot VM上是这样 - 而您还会使用什么?)
Hotspot中有一种并发标记和清除实现(尽管我不确定默认情况下是否激活),但这会产生一些额外的开销,并且仍然必须在执行清除之前停止所有线程。因此,基本上无论您从哪个线程执行System.gc(),VM都将等待所有线程达到安全点,停止它们,然后进行收集。因此,使用线程完全没有意义。

0

我完全同意通常不建议显式调用垃圾回收器。它可能会使应用程序停顿几秒钟,甚至几分钟。如果您正在处理后台服务,则通常不是问题,但如果暴露给用户,则会导致不良体验。

然而,在极端情况下,当内存非常短缺且应用程序可能崩溃时,您将希望这样做。但是,在应用程序的设计和实现中可以采取很多措施来避免这些情况。

Java 中一个我喜欢的功能是WeakReference对象,它们可以回收内存并避免内存泄漏。它们是您的好朋友。


当内存非常短缺且应用程序可能崩溃时,如果应用程序的内存不足,JVM将在失败之前尝试进行垃圾回收,因此没有好的理由。 - Voo

0

正如其他人所说,调用System.gc()通常是一个错误。(并非总是如此...)

假设您有其中一种罕见的用例,调用System.gc()可能会有益处,则有两种情况需要考虑:

  • 如果您的JVM正在使用经典的“停止所有线程”收集器,则在单独的线程中运行它不会有任何区别。GC会在整个过程中停止所有应用程序线程。

  • 如果您的JVM正在运行并发收集器,则在单独的线程中运行它可能是一个好主意。虽然所有收集器都有一个阶段,在该阶段中停止所有线程,但是调用System.gc()被记录为在“Java虚拟机已尽最大努力从所有丢弃的对象中回收空间之后”返回。因此,在单独的线程中运行它将允许当前线程执行其他操作。但是,您需要小心,避免启动多个线程,每个线程都调用System.gc();即负责启动线程的代码需要跟踪任何先前的GC线程。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接