您不应该将所有的
finalize()
方法替换为
Cleaner
。
finalize()
方法的弃用和(一个
public
)
Cleaner
的引入发生在同一个Java版本中,这只是表明了关于该主题的一般性工作已经完成,而不是要求一个替代另一个。
该Java版本的其他相关工作包括删除一个规则,即PhantomReference
不会自动清除(是的,在Java 9之前,使用PhantomReference
而不是finalize()
仍需要两个GC循环才能回收对象),以及引入Reference.reachabilityFence(…)
。
第一种替代finalize()
的方法是根本不要有垃圾回收依赖操作。当您说您没有使用许多时,这很好,但我在野外看到完全过时的finalize()
方法。问题在于,finalize()
看起来像是普通的protected
方法,并且finalize()
是某种类型的析构函数的顽固神话仍然在一些互联网页面上传播。将其标记为已弃用可以向开发人员发出信号,表明这不是情况,而不会破坏兼容性。使用需要显式注册的机制有助于理解这不是正常程序流程。当它看起来比覆盖单个方法更复杂时,它也不会受到伤害。
如果您的类封装了非堆资源,则文档中指出:
持有非堆资源实例的类应提供一种方法以启用对这些资源的显式释放,并在适当时还应实现AutoCloseable。
(这是首选解决方案)
Cleaner和PhantomReference在对象变得不可达时提供了更灵活、更高效的释放资源的方式。
因此,当您确实需要与垃圾收集器交互时,即使这个简短的文档注释没有提到PhantomReference
作为Cleaner
的隐藏后端,它也命名了两个替代方案;直接使用PhantomReference
是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,其中一个finalizer关闭了仍在使用的执行器服务。
当然,仅使用Cleaner
或PhantomReference
并不能解决这个问题。但是移除finalizers并在真正需要时实现替代机制,就是一个仔细思考这个主题的机会,也许可以在必要的地方插入reachabilityFence
s。最糟糕的情况是,你可能拥有一个看起来易于使用的方法,但实际上这个主题非常复杂,99%的使用方式都可能在将来某一天出现问题。
此外,虽然替代方案更加复杂,但你自己说过,它们很少被需要。这种复杂性应该只影响到代码库的一小部分。而且,为什么java.lang.Object
,作为所有类的基类,还要包含一个解决Java编程中罕见情况的方法呢?
finalize()
。Javadoc明确表示,当一个对象变得不可达时,“Cleaner”和“PhantomReference”提供了更灵活、更高效的释放资源的方式。请注意,Javadoc中使用了“显式地”一词。 - Elliott Frisch