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

2022-09-01 14:05:06

完这个问题后,我想起了我被教Java的时候,并被告知永远不要调用finize()或运行垃圾收集器,因为“这是一个大黑匣子,你永远不需要担心”。有人能把这个理由归结为几句话吗?我相信我可以阅读Sun关于此事的技术报告,但我认为一个漂亮,简短,简单的答案会满足我的好奇心。


答案 1

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

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

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

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

您正在考虑的 Sun 文档在这里:Java SE 6 HotSpot™ 虚拟机垃圾回收调优

(您可能不知道的另一件事是:在对象上实现finize()方法会使垃圾回收变慢。首先,需要次GC运行来收集对象:一次运行finize(),另一次运行以确保对象在最终确定过程中没有复活。其次,具有finize()方法的对象必须被GC视为特殊情况,因为它们必须单独收集,它们不能被批量丢弃。


答案 2

不要为终结器而烦恼。

切换到增量垃圾回收。

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

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

与此非常相关的是,如果您使用的是序列化,并且已经序列化了临时对象,则需要通过调用 ObjectOutputStream.reset() 来清除序列化缓存,否则您的进程将泄漏内存并最终死亡。缺点是非瞬态对象将被重新序列化。序列化临时结果对象可能比您想象的要混乱一些!

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

避开幻像引用和弱引用,除非你真的兴奋。

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

不,我不是在开玩笑。

参考实现可以免费下载,SUN的Peter Dibbles书真的很好读。