不应将所有方法替换为 .在同一 Java 版本中,方法的弃用和 (a) 的引入这一事实仅表明发生了关于该主题的一般工作,而不是一个应该替代另一个。finalize()
Cleaner
finalize()
public
Cleaner
该Java版本的其他相关工作是删除不自动清除的规则(是的,在Java 9之前,使用a而不是仍然需要两个GC周期来回收对象)和引入Reflection.reachabilityFence(...)
。PhantomReference
PhantomReference
finalize()
的第一个替代方法是根本不具有依赖于垃圾回收的操作。当你说你没有很多时,这很好,但我在野外看到了完全过时的方法。问题是,这看起来像是一种普通的方法,而某种破坏者的顽固神话仍然在一些互联网页面上传播。将其标记为已弃用允许向开发人员发出信号,表明情况并非如此,而不会破坏兼容性。使用需要显式注册的机制有助于了解这不是正常的程序流。当它看起来比覆盖单个方法更复杂时,它并没有什么坏处。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()
-
正如您已经发现的那样,有一种方法,该方法允许立即执行清理操作并删除清理程序。所以在提供显式的 close 方法甚至实现时,这是首选的清理方式,及时处置资源,摆脱垃圾回收器清理的所有问题。clean()
AutoClosable
请注意,这与上述要点相协调。一个对象可以有多个清理器,例如,由层次结构中的不同类注册。它们中的每一个都可以单独触发,具有关于访问权限的固有解决方案,只有注册了清理程序的人才能获得关联的人才能调用该方法。Cleanable
clean()
也就是说,经常被忽视的是,使用垃圾回收器管理资源时可能发生的最糟糕的事情并不是清理操作可能稍后运行或根本不运行。可能发生的最糟糕的事情是它运行得太早了。例如,请参阅 Java 8 中对强可访问对象调用的 finalize()。或者,一个非常好的,JDK-8145304,Executors.newSingleThreadExecutor().submit(runnable)抛出DenisdenExecutionException,其中终结器关闭仍在使用的执行器服务。
当然,只是使用或不能解决这个问题。但是,删除终结器并在真正需要时实施替代机制,是仔细考虑该主题的机会,并且可能在需要的地方插入可访问性Fence
。你可以拥有的最糟糕的事情是一种看起来易于使用的方法,而实际上,这个主题非常复杂,99%的使用可能会在某一天被打破。Cleaner
PhantomReference
此外,虽然替代方案更复杂,但你自己也说过,它们很少被需要。这种复杂性应该只影响代码库的一小部分。为什么所有类的基类都应该托管一个解决Java编程中罕见的角落情况的方法?java.lang.Object