当前位置: 首页 > 知识库问答 >
问题:

Java9清洁器应该比最终版本更好吗?

丌官翰采
2023-03-14

在Java中,重写finalize方法会受到很大的批评,尽管我不明白这是为什么。像FileInputStream这样的类使用它来确保在Java8和Java10中调用Close。然而,Java9引入了Java.lang.ref.cleanle,它使用PhantomReference机制而不是GC终结。一开始,我认为这只是一种向第三方类添加终结的方法。但是,它的javadoc中给出的示例显示了一个可以很容易地用终结器重写的用例。

我是否应该用清洁器重写所有的finalize方法?(当然,我没有很多。只有一些使用OS资源的类,特别是用于CUDA Interop。)

正如我所知,Cleaner(通过PhantomReference)避免了finalizer的一些危险。特别是,您没有任何访问已清除对象的权限,因此您不能复活它或它的任何字段。

然而,那是我能看到的唯一优势。清洁工也不是微不足道的。事实上,它和终结都使用referenceQueue!(难道您不喜欢阅读JDK是多么容易吗?)比定稿快吗?它是否避免了等待两个GC?如果许多对象排队清理,它会避免堆耗尽吗?(在我看来,所有这些问题的答案都是否定的。)

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

共有1个答案

裴弘
2023-03-14

不应该用清理器替换所有finalize()方法。在同一个Java版本中出现了finalize()方法的废弃和(public)cleaner的引入,这只是表明在这个主题上进行了一般性的工作,而不是认为其中一个可以替代另一个。

该Java版本的其他相关工作是删除PhantomReference不会自动清除的规则(是的,在Java9之前,使用PhantomReference而不是Finalize()仍然需要两个GC循环才能回收对象)和引入Reference.ReachabilityFence(…)

finalize()的第一个替代方法是根本不使用与垃圾收集相关的操作。当您说您没有很多方法时,这是很好的,但是我在野外看到过完全过时的finalize()方法。问题是,finalize()看起来像是一个普通的protected方法,而且finalize()是某种析构函数的说法仍然在一些internet页面上流传。将其标记为不推荐允许向开发人员发出信号,表明情况并非如此,而不会破坏兼容性。使用要求显式注册的机制有助于理解这不是正常的程序流。而且,当它看起来比重写单个方法更复杂时,它也没有坏处。

如果您的类封装了非堆资源,则文档声明:

实例中包含非堆资源的类应该提供一种方法来实现这些资源的显式释放,如果适当的话,它们还应该实现AutoCloseable。

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

Cleaner和PhantomReference提供了在无法访问对象时释放资源的更灵活有效的方法。

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

但是,即使清洁器也能解决您所知道的更多问题。

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

>

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

    注册清理器需要一个完整构造的runnable,其中包含清理所需的所有数据,而不需要引用正在构造的对象。当构造函数中没有进行资源分配时,您甚至可以推迟注册(考虑未绑定的套接字实例或未原子连接到显示器的框架

    可以重写finalize()方法,而无需调用超类方法,也无需在例外情况下调用超类方法。通过将方法声明为final来阻止方法重写,根本不允许子类进行此类清理操作。相反,每个类都可以注册清洁工,而不干扰其他清洁工。

    诚然,您可以用封装的对象解决这些问题,但是,为每个类提供finalize()方法的设计却指向了另一个错误的方向。

    >

  • 正如您已经发现的,有一个clean()方法,它允许立即执行清理操作并删除清理器。因此,当提供显式关闭方法,甚至实现可自动关闭时,这是首选的清理方式,可以及时处理资源,并摆脱基于垃圾收集器的清理的所有问题。

    请注意,这与上面提到的要点相一致。一个对象可以有多个清除器,例如,由层次结构中的不同类注册。它们中的每一个都可以单独触发,使用关于访问权限的内在解决方案,只有注册了清洁器的人才能获得相关的cleanable,从而能够调用clean()方法。

    也就是说,经常被忽视的是,当使用垃圾收集器管理资源时,可能发生的最糟糕的事情不是清理操作可能稍后运行或根本不运行。可能发生的最糟糕的事情,是它运行得太早。例如,请参见Java8中对强可达对象调用的finalize()。或者,JDK-8145304,executors.newsinglethreadexecutor().submit(runnable)抛出RejectedExecutionException,其中终结器关闭仍在使用的executor服务。

    诚然,仅仅使用cleanerphantomreference并不能解决这个问题。但是,删除终结器并在真正需要时实现替代机制是一个机会,可以仔细考虑主题,并在需要的地方插入reachabilityFence。你所能拥有的最糟糕的事情是,一个看起来很容易使用的方法,而实际上,这个主题非常复杂,99%的使用都有可能在某一天失败。

    此外,尽管替代方案更加复杂,但你自己也说过,它们很少被需要。这种复杂性只会影响代码库的一小部分。为什么所有类的基类Java.lang.object要承载一个方法来解决Java编程中的一个罕见的问题呢?

  •  类似资料:
    • 问题内容: 我正在尝试将ant项目从Java 7升级到Java 8 (在“ Eclipse Kepler”中部署的项目,其“对Eclipse Kepler SR2的Java™8支持”) 为此,我下载了ant 1.9.4(根据这篇文章http://wiki.eclipse.org/Ant/Java8),并将其配置为我的“蚂蚁之家”。 当我尝试编译时收到以下警告: 但是为工作空间定义的编译器是1.8。

    • 问题内容: 从我有很多的用户输入和…此刻我总是写.. 我想知道您是否可以创建一个可以立即保护,转义和清理/ 数组的函数,因此您不必在每次使用用户输入等时都进行处理。 我在想一个功能,例如,和它里面,它应该做的,,,(我想这是所有做它的清洁与安全),然后返回。 那有可能吗?制作一个适用于和的函数,因此您只需执行以下操作: 因此,在以后的代码中,当您使用eg 或时,它们是否已固定,剥离等? 试了一下自

    • java应该吗 java版本“1.8.0_251”java(TM)SE运行时环境(构建1.8.0_251-b08)java热点(TM)64位服务器虚拟机(构建25.251-b08,混合模式) 和 javac 1.8.0_181 如果不是同一个版本,有什么问题吗?

    • 我想知道是否有人能帮上忙。清理和构建最简单的项目时出错。(见下文) 导致错误的步骤: 作为一名新员工,也就是一台不超过4个月的机器上的新用户,我从打开NetBeans 8.2开始——一周前为我安装了NetBeans和Android Studio(用于sdk)。 但是没有安装Android插件,所以我实现了nbandroid更新中心来安装插件。然后我连接了sdk——在users\user中找到。us

    • 将清单中的compileSdkVersion设置为小于最新的api内部版本号有什么好处吗,还是您应该始终将其设置为最新的api内部版本? 我说的优势是指应用程序的性能、应用程序的编译时间、apk的大小等。当然,我指的是你可以把它设置得更低的情况--例如,一个应用程序不使用共享转换或任何Lollipop的功能。

    • 问题内容: 可以从许多线程访问类。在这种情况下,必须是记录器还是最终的和静态的?谢谢。 问题答案: 所有主要的Java日志记录程序包(等)都是同步的并且是线程安全的。即使从多个线程调用该类,每个类的记录器的标准模式也可以。