为什么不显式调用finalize()或者启动垃圾收集器?
阅读完这个问题后,我想起了什么时候教我Java的,什么时候告诉我永远不要调用finalize()或者运行垃圾收集器,因为"这是一个大黑盒子,我们无需担心"。有人可以将其理由简化为几句话吗?我敢肯定,我可以阅读Sun的有关此事的技术报告,但是我认为,一个很好的,简短的,简单的答案可以满足我的好奇心。
解决方案
回答
GC对何时正确完成操作进行了很多优化。
因此,除非我们熟悉GC的实际工作方式以及如何标记世代,否则手动调用finalize或者启动GC'ing可能会损害性能而不是帮助。
回答
假设终结器与它们的.NET命名相似,那么仅当我们拥有诸如文件句柄之类的资源时,才真正需要调用这些终结器。大多数情况下,对象没有这些引用,因此不需要调用它们。
尝试收集垃圾是很糟糕的,因为它实际上不是垃圾。我们已经告诉VM在创建对象时分配一些内存,并且垃圾收集器隐藏了有关那些对象的信息。在内部,GC正在对其分配的内存进行优化。当我们手动尝试收集垃圾时,我们不知道GC想要保留和清除的内容,我们只是在强迫它。结果,我们弄乱了内部计算。
如果我们对GC内部的内容了解更多,则可以做出更明智的决定,但是我们就错过了GC的好处。
回答
简短的答案: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的书非常好阅读。
回答
避免终结器。无法保证会及时调用它们。内存管理系统(即垃圾收集器)决定用终结器收集对象可能需要很长时间。
许多人使用终结器来完成诸如关闭套接字连接或者删除临时文件之类的操作。这样做会使应用程序行为无法预测,并与JVM将GC传递给对象的时间有关。这可能导致"内存不足"的情况,这不是由于Java Heap耗尽了,而是由于系统用完了特定资源的句柄而导致的。
要记住的另一件事是,在环境中引入对System.gc()的调用或者此类锤子可能会显示出良好的效果,但不一定会转换为其他系统。并非每个人都运行相同的JVM,有很多,例如SUN,IBM J9,BEA JRockit,Harmony,OpenJDK等。此JVM都符合JCK(即经过正式测试的JVM),但是有很多加快速度的自由。 GC是每个人都大力投资的领域之一。使用锤子常常会破坏这种努力。
回答
至于终结器:
- 他们实际上是无用的。不能保证及时调用它们,或者根本不能保证它们被调用(如果GC永不运行,则终结器也不会)。这意味着我们通常不应该依赖它们。
- 终结器不保证是幂等的。垃圾收集器非常注意保证它永远不会在同一对象上多次调用
finalize()
。对于编写良好的对象而言,这并不重要,但是对于编写不良的对象,多次调用finalize可能会导致问题(例如,双重释放本机资源...崩溃)。 - 每个具有" finalize()"方法的对象也应提供" close()"(或者类似方法)。这是我们应该调用的函数。例如," FileInputStream.close()"。当我们有一个打算由我们调用的更合适的方法时,没有理由调用
finalize()
。