您不应该finalize()
用Cleaner
. 方法的弃用和 (a )finalize()
的引入发生在同一个 Java 版本中的事实仅表明发生了关于该主题的一般工作,而不是应该替代另一个。public
Cleaner
该 Java 版本的其他相关工作是删除了 aPhantomReference
不会自动清除的规则(是的,在 Java 9 之前,使用 aPhantomReference
而不是finalize()
仍然需要两个 GC 周期来回收对象)并引入Reference.reachabilityFence(…)
.
的第一个替代方案finalize()
是根本没有垃圾收集相关操作。当你说你没有很多时这很好,但我finalize()
在野外看到了完全过时的方法。问题是,这finalize()
看起来是一种普通的protected
方法,而作为某种破坏者的顽固神话finalize()
仍然在一些互联网页面上传播。将其标记为已弃用可以向开发人员发出信号,表明情况并非如此,而不会破坏兼容性。使用需要显式注册的机制有助于理解这不是正常的程序流程。当它看起来比覆盖单个方法更复杂时,它不会受到伤害。
如果您的类确实封装了非堆资源,文档说明:
实例持有非堆资源的类应该提供一种方法来启用这些资源的显式释放,并且如果适当,它们还应该实现AutoCloseable。
(所以这是首选的解决方案)
Cleaner和PhantomReference提供了更灵活、更有效的方法来在对象变得无法访问时释放资源。
因此,当您真正需要与垃圾收集器交互时,即使是这个简短的文档注释也列出了两个替代方案,因为PhantomReference
这里没有提到开发人员隐藏的后端Cleaner
;usingPhantomReference
是 的替代方法Cleaner
,使用起来可能更复杂,但也提供了对时间和线程的更多控制,包括在使用资源的同一线程内进行清理的可能性。(与WeakHashMap
相比,它有这样的清理避免了线程安全构造的开销)。它还允许处理在清理过程中抛出的异常,以比默默吞下它们更好的方式。
但甚至Cleaner
可以解决更多您知道的问题。
一个重要的问题是注册时间。
执行构造函数时,注册具有非平凡finalize()
方法的类的对象。Object()
此时,对象还没有被初始化。如果您的初始化因异常而终止,该finalize()
方法仍将被调用。通过对象的数据来解决这个问题可能很诱人,例如将initialized
标志设置为true
,但您只能对您自己的实例数据这么说,而对于子类的数据则不能这样说,子类的数据在您的构造函数返回时仍未初始化。
注册一个清理器需要一个完整的构造器Runnable
,它包含清理所需的所有数据,而不需要引用正在构造的对象。您甚至可以在构造函数中没有发生资源分配时推迟注册(想想未绑定的Socket
实例或未Frame
原子连接到显示器的实例)
可以finalize()
重写方法,而无需调用超类方法或在异常情况下无法执行此操作。通过声明它来防止方法被覆盖,final
根本不允许子类有这样的清理动作。相反,每个班级都可以注册清洁工,而不会干扰其他清洁工。
当然,您可以使用封装对象解决此类问题,但是,finalize()
为每个类提供一个方法的设计被引导到另一个错误的方向。
正如您已经发现的那样,有一种clean()
方法可以立即执行清理操作并删除清洁器。所以在提供显式关闭方法甚至实现AutoClosable
时,这是清理的首选方式,及时处置资源,摆脱基于垃圾收集器清理的所有问题。
请注意,这与上述要点相协调。一个对象可以有多个清理器,例如由层次结构中的不同类注册。它们中的每一个都可以单独触发,具有关于访问权限的内在解决方案,只有注册了清洁器的人才能获得关联Cleanable
的人才能调用该clean()
方法。
也就是说,经常被忽视的是,在使用垃圾收集器管理资源时可能发生的最糟糕的事情并不是清理操作可能会稍后运行或根本不会运行。可能发生的最糟糕的事情是它运行得太早了。例如,请参阅Java 8 中对强可达对象调用的 finalize()。或者,一个非常好的,JDK-8145304,Executors.newSingleThreadExecutor().submit(runnable) 抛出 RejectedExecutionException,其中终结器关闭仍在使用的执行器服务。
当然,只是使用Cleaner
或PhantomReference
不解决这个问题。但是在真正需要时删除终结器并实施替代机制是一个仔细考虑主题并可能在需要的地方插入reachabilityFence
s的机会。您可能拥有的最糟糕的事情是一种看起来易于使用的方法,而实际上,该主题非常复杂,并且其 99% 的使用可能有一天会中断。
此外,虽然替代方案更复杂,但您自己说过,它们很少需要。这种复杂性只会影响您代码库的一小部分。为什么java.lang.Object
所有类的基类都应该承载一个解决 Java 编程中罕见的极端情况的方法?