为什么不显式调用finalize()或启动垃圾收集器?

时间:2008-08-26 19:46:46

标签: java garbage-collection

在阅读this question之后,我被提醒我,当我被教授Java并告诉他们永远不要调用finalize()或运行垃圾收集器,因为“这是一个你永远不必担心的大黑盒子”。有人可以将这个推理归结为几句话吗?我确信我可以阅读Sun关于此事的技术报告,但我认为一个简短的,简短的答案可以满足我的好奇心。

7 个答案:

答案 0 :(得分:43)

简短的回答:Java垃圾收集是一个非常精细的调整工具。 System.gc()是一把大锤。

Java的堆分为不同的代,每个代都使用不同的策略收集。如果你将一个探查器附加到一个健康的应用程序,你会发现它很少需要运行最昂贵的集合,因为大多数对象都被年轻一代中速度较快的复制收集器捕获。

直接调用System.gc(),虽然在技术上无法保证做任何事情,但实际上会触发一个昂贵的,世界各地的完整堆集合。这几乎总是错误的做法。你认为你正在节省资源,但实际上你没有充分的理由浪费它们,迫使Java重新检查你所有的活动对象“以防万一”。

如果您在关键时刻遇到GC暂停问题,最好配置JVM以使用并发标记/扫描收集器,该收集器专门用于最大限度地减少暂停时间,而不是尝试将大锤用于问题,只是进一步打破它。

您正在考虑的Sun文档位于:Java SE 6 HotSpot™ Virtual Machine Garbage Collection Tuning

(你可能不知道的另一件事:在你的对象上实现finalize()方法会使垃圾收集速度变慢。首先,它需要两个 GC运行来收集对象:一个运行finalize(并且确保对象在最终确定期间没有复活。其次,具有finalize()方法的对象必须被GC视为特殊情况,因为它们必须单独收集,它们不能被丢弃散装。)

答案 1 :(得分:4)

不要打扰终结者。

切换到增量垃圾收集。

如果要帮助垃圾收集器,请将对不再需要的对象的引用置空。较少的路径=更明确的垃圾。

不要忘记(非静态)内部类实例保留对其父类实例的引用。因此,内部阶级线程比你预期的要多得多。

在一个非常相关的方面,如果您正在使用序列化,并且您已经序列化了临时对象,那么您将需要通过调用ObjectOutputStream.reset()清除序列化缓存,否则您的进程将泄漏内存和最终死了。 缺点是非瞬态对象将被重新序列化。 序列化临时结果对象可能比你想象的要麻烦一些!

考虑使用软引用。如果您不知道软引用是什么,请阅读java.lang.ref.SoftReference的javadoc

避免使用幻影参考和弱引用,除非你真的感到兴奋。

最后,如果你真的不能容忍GC使用Realtime Java。

不,我不是在开玩笑。

参考实施可以免费下载,来自SUN的Peter Dibbles书非常好读。

答案 2 :(得分:4)

至于终结者:

  1. 他们几乎没用。它们不能保证及时被调用,或者实际上根本不被调用(如果GC从未运行,也不会有任何终结器)。这意味着你通常不应该依赖它们。
  2. 终结者不能保证是幂等的。垃圾收集器非常谨慎,以确保它永远不会在同一个对象上多次调用finalize()。使用编写良好的对象,无关紧要,但是如果编写的对象写得不好,多次调用finalize会导致问题(例如,本机资源的双重释放......崩溃)。
  3. 具有finalize()方法的每个对象还应提供close()(或类似)方法。这是你应该打电话的功能。例如,FileInputStream.close()。如果您有一个更适合 的方法,则没有理由致电finalize()

答案 3 :(得分:1)

假设终结器类似于它们的.NET同名,那么当你有可能泄漏的文件句柄等资源时,你只需要调用它们。大多数情况下,您的对象没有这些引用,因此不需要调用它们。

尝试收集垃圾是不好的,因为它不是真正的垃圾。您已告知VM在创建对象时分配一些内存,并且垃圾收集器正在隐藏有关这些对象的信息。在内部,GC正在对其进行的内存分配执行优化。当你手动尝试收集垃圾时,你不知道GC想要抓住什么并摆脱它,你只是强迫它的手。结果你弄乱了内部计算。

如果你对内部GC的内容有了更多的了解,那么你可以做出更明智的决定,但是你已经错过了GC的好处。

答案 4 :(得分:1)

在finalize中关闭OS句柄的真正问题是finalize以无保证的顺序执行。但是如果你有阻止事物的句柄(比如插座),你的代码可能会陷入死锁状态(根本不是微不足道)。

所以我要以可预测的有序方式显式关闭句柄。基本上,处理资源的代码应遵循以下模式:

SomeStream s = null;
...
try{
   s = openStream();
   ....
   s.io();
   ...
} finally {
   if (s != null) {
       s.close();
       s = null;
   }
}

如果你编写自己的类,通过JNI和打开句柄工作,它会变得更加复杂。您需要确保句柄已关闭(已释放)并且只会发生一次。 Desktop J2SE中经常忽略的OS句柄是Graphics[2D]。即使BufferedImage.getGrpahics()可能会返回指向视频驱动程序的句柄(实际上是在GPU上保存资源)。如果你不自己发布并留下垃圾收集器来完成工作 - 你可能会发现奇怪的OutOfMemory和类似的情况,当你用完视频卡映射的位图但仍然有足够的内存。根据我的经验,它经常发生在使用图形对象的紧密循环中(提取缩略图,缩放,锐化你的名字)。

基本上GC不会照顾程序员正确的资源管理责任。它只需要记忆而不需要其他任何东西。调用close()IMHO的Stream.finalize将更好地实现抛出异常新的RuntimeError(“垃圾收集仍然打开的流”)。在业余爱好者不知所措后,它将节省数小时和数天的调试和清洁代码。

快乐的编码。

和平。

答案 5 :(得分:0)

GC在何时正确完成任务时进行了大量优化。

因此,除非您熟悉GC的实际工作方式以及标记生成的方式,否则手动调用finalize或启动GC可能会损害性能而不是帮助。

答案 6 :(得分:0)

避免使用终结者。无法保证及时拨打电话。在内存管理系统(即垃圾收集器)决定使用终结器收集对象之前可能需要相当长的时间。

许多人使用终结器来执行关闭套接字连接或删除临时文件等操作。通过这样做,您可以使应用程序行为变得不可预测,并且与JVM转向GC对象时绑定在一起。这可能导致“内存不足”情况,不是由于Java堆耗尽,而是由于系统耗尽特定资源的句柄。

要记住的另一件事是,将调用引入System.gc()或此类锤子可能会在您的环境中显示出良好的效果,但它们不一定会转换为其他系统。不是每个人都运行相同的JVM,有很多,SUN,IBM J9,BEA JRockit,Harmony,OpenJDK等......这个JVM都符合JCK(已经过官方测试的那些),但有很多快速做事的自由。 GC是每个人都投入巨资的领域之一。使用锤子通常会破坏这种努力。