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

在读完这个问题后 ,我被提醒我什么时候被Java教过,并告诉他们永远不要调用finalize()或者运行垃圾收集器,因为“这是一个你永远不用担心的大黑盒子”。 有人可以将这个推理归结为几句吗? 我确信我可以在这件事上阅读Sun的技术报告,但我认为一个好的,简短的答案可以满足我的好奇心。

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

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

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

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

您正在考虑的Sun文档位于: Java SE 6 HotSpot™虚拟机垃圾收集调整

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

不要打扰终结者。

切换到增量垃圾收集。

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

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

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

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

除非您真的感到兴奋,否则请避开幻影参考和弱引用。

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

不,我不是在开玩笑。

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

对于终结者来说:

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

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

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

如果您对内部GC的内容有了更多了解,那么您可以做出更明智的决策,但之后您就错过了GC的优势。

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

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

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

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

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

快乐的编码。

和平。

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

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

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

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

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