Java 9 Cleaner 应该优先于最终确定吗?

2022-09-01 23:38:28

在Java中,重写该方法会得到一个不好的说唱,尽管我不明白为什么。像这样的类用它来确保在Java 8和Java 10中被调用。尽管如此,Java 9还是引入了使用PhantomReference机制而不是GC终结。起初,我认为这只是将最终确定添加到第三方类的一种方式。但是,其javadoc中给出的示例显示了一个用例,该用例可以很容易地用终结器重写。finalizeFileInputStreamclosejava.lang.ref.Cleaner

我应该用Cleaner重写我所有的方法吗?(当然,我没有很多。只是一些使用操作系统资源的类,特别是用于 CUDA 互操作。finalize

据我所知,Cleaner(通过PhantomReference)避免了.特别是,您无权访问已清理的对象,因此无法复活它或其任何字段。finalizer

但是,这是我能看到的唯一优势。清洁剂也并非易事。事实上,它和finization都使用!(你不就是喜欢阅读JDK是多么容易吗?它比最终确定更快吗?它是否避免等待两个指导性案例?如果许多对象排队等待清理,它能否避免堆耗尽?(在我看来,所有这些问题的答案是否定的。ReferenceQueue

最后,实际上没有什么可以保证阻止您在清理操作中引用目标对象。请仔细阅读长长的 API 说明!如果你最终引用了对象,整个机制将悄无声息地中断,不像最终确定总是试图一瘸一拐地前进。最后,虽然终结线程由 JVM 管理,但创建和持有清理线程是您自己的责任。


答案 1

不应将所有方法替换为 .在同一 Java 版本中,方法的弃用和 (a) 的引入这一事实仅表明发生了关于该主题的一般工作,而不是一个应该替代另一个。finalize()Cleanerfinalize()publicCleaner

该Java版本的其他相关工作是删除不自动清除的规则(是的,在Java 9之前,使用a而不是仍然需要两个GC周期来回收对象)和引入Reflection.reachabilityFence(...)PhantomReferencePhantomReferencefinalize()

的第一个替代方法是根本不具有依赖于垃圾回收的操作。当你说你没有很多时,这很好,但我在野外看到了完全过时的方法。问题是,这看起来像是一种普通的方法,而某种破坏者的顽固神话仍然在一些互联网页面上传播。将其标记为已弃用允许向开发人员发出信号,表明情况并非如此,而不会破坏兼容性。使用需要显式注册的机制有助于了解这不是正常的程序流。当它看起来比覆盖单个方法更复杂时,它并没有什么坏处。finalize()finalize()finalize()protectedfinalize()

如果您的类确实封装了非堆资源,则文档指出:

其实例包含非堆资源的类应提供一种方法来启用这些资源的显式释放,并且它们还应在适当的情况下实现 AutoCloseable

(所以这是首选的解决方案)

CleanerPhantomReference 提供了更灵活、更有效的方法,以便在对象变得无法访问时释放资源。

因此,当您确实需要与垃圾回收器进行交互时,即使是这个简短的文档注释也会列出种选择,因为这里没有提到隐藏的开发人员后端;直接使用 是 的替代方法,后者使用起来可能更加复杂,但也提供了对时序和线程的更多控制,包括在使用资源的同一线程中进行清理的可能性。(与 相比,它具有这样的清理功能,避免了线程安全构造的费用)。它还允许处理清理期间引发的异常,比默默吞下它们更好。PhantomReferenceCleanerPhantomReferenceCleanerWeakHashMap

但甚至可以解决您意识到的更多问题。Cleaner

一个重要的问题是注册的时间。

  • 具有非平凡方法的类的对象在执行构造函数时注册。此时,该对象尚未初始化。如果初始化因异常而终止,则仍将调用该方法。通过对象的数据来解决这个问题可能很诱人,例如,将标志设置为 ,但是您只能为自己的实例数据说这句话,而不能为子类的数据说,当构造函数返回时,子类的数据仍未初始化。finalize()Object()finalize()initializedtrue

    注册清理程序需要一个完全构造的,其中包含清理所需的所有数据,而不引用正在构造的对象。当构造函数中没有发生资源分配时,您甚至可以推迟注册(想想未绑定的实例或未原子连接到显示器的实例)RunnableSocketFrame

  • 可以重写方法,而无需调用超类方法或在特殊情况下无法执行此操作。通过声明方法来防止该方法重写,根本不允许子类具有此类清理操作。相反,每个类都可以注册清洁程序,而不会干扰其他清洁程序。finalize()final

当然,你可以用封装的对象来解决这样的问题,但是,设计一个针对每个类的方法被引导到另一个错误的方向。finalize()

  • 正如您已经发现的那样,有一种方法,该方法允许立即执行清理操作并删除清理程序。所以在提供显式的 close 方法甚至实现时,这是首选的清理方式,及时处置资源,摆脱垃圾回收器清理的所有问题。clean()AutoClosable

    请注意,这与上述要点相协调。一个对象可以有多个清理器,例如,由层次结构中的不同类注册。它们中的每一个都可以单独触发,具有关于访问权限的固有解决方案,只有注册了清理程序的人才能获得关联的人才能调用该方法。Cleanableclean()


也就是说,经常被忽视的是,使用垃圾回收器管理资源时可能发生的最糟糕的事情并不是清理操作可能稍后运行或根本不运行。可能发生的最糟糕的事情是它运行得太早了。例如,请参阅 Java 8 中对强可访问对象调用的 finalize()。或者,一个非常好的,JDK-8145304,Executors.newSingleThreadExecutor().submit(runnable)抛出DenisdenExecutionException,其中终结器关闭仍在使用的执行器服务。

当然,只是使用或不能解决这个问题。但是,删除终结器并在真正需要时实施替代机制,是仔细考虑该主题的机会,并且可能在需要的地方插入可访问性Fence。你可以拥有的最糟糕的事情是一种看起来易于使用的方法,而实际上,这个主题非常复杂,99%的使用可能会在某一天被打破。CleanerPhantomReference

此外,虽然替代方案更复杂,但你自己也说过,它们很少被需要。这种复杂性应该只影响代码库的一小部分。为什么所有类的基类都应该托管一个解决Java编程中罕见的角落情况的方法?java.lang.Object


答案 2

正如 Elliott 在注释中指出的那样,在 Java9+ 上继续前进时,已被弃用,因此使用 实现方法更有意义。另外,从发行说明中:Object.finalizeCleaner

该方法已被弃用。最终确定机制本质上是有问题的,并且可能导致性能问题、死锁和挂起。和 提供了更灵活、更有效的方法,以便在对象变得无法访问时释放资源。java.lang.Object.finalizejava.lang.ref.Cleanerjava.lang.ref.PhantomReference

错误数据库 - JDK-8165641 中的详细信息


推荐