在Java9中,已经不再推荐使用finalizers,取而代之的是引入了cleaners。这两者有什么区别呢?

11

在Java9中,终结器(finalizers)已经被弃用,引入了清理器(cleaners)的新概念。这个具体原因是什么?是否有任何特定场景或原因可以优先考虑使用清理器而不是终结器(尽管它们都不被推荐)?

2个回答

10

弃用Finalizer的原因在于:

Finalizer存在问题,使用可能导致性能问题、死锁、挂起和其他问题。

此外,终结时间不可预测,不能保证一定会调用终结器。持有非堆资源实例的类应提供一种方法来显式释放这些资源,并且如果适当,则应实现java.lang.AutoCloseable

作为Finalizer替代方案的Cleaners可以提供对象清理函数的简单注册和取消。

CleanerPhantomReference提供了更加灵活高效的方式,在对象变得不可访问时释放资源。

应用程序可创建自己的清理服务,并在不再使用时终止该服务。

使用方法:一旦对象已成为虚引用可达,则对同一对象的清理操作由Cleaner注册并管理。将对象引用和相应的清理操作注册后,将返回一个Cleanable对象。最有效的使用方式是在关闭对象或不再需要时显式调用clean方法。

注意:在Java9之前,一个类似的Cleaner实现也存在于sun.misc包下。详情请参考此处


3
我建议尽量避免使用finalizer或者cleaner。相反,应该使类实现AutoCloseable并在close()方法中进行资源清理。
Cleaner是一种尝试实现类似finalizer功能的机制,但是编写正确的Cleaner非常棘手。
什么是Finalizer攻击? 当对象没有完全构造时(例如在构造函数内抛出异常),Finalizer会运行。攻击者可以创建一个子类,并重写它的finalize方法,可能绕过构造函数内部进行的安全检查。
那么为什么要使用Cleaner呢?
1. 不会污染类的公共API(因为Cleaners和相关对象是私有的); 2. 不需要创建空的final finalize方法,即使不需要,也无需担心子类将其作为攻击的一部分覆盖掉; 3. Cleaner只运行一次。
编辑:经过考虑,我意识到Cleaner不能防止Finalizer攻击。因此,即使在非final类中使用Cleaner,请创建一个空的final finalize方法。

1
对Point4感到困惑。这是否意味着终结器可能会运行多次? - Chota Bheem
6
我肯定不会用空的 finalize() 方法污染我的代码。只有在以下3种情况下才与您有关:1)您的代码真正与安全性相关,2)您允许不受信任的代码对您的类进行子类化 并且 3)在验证参数之前,您的构造函数使对象处于可利用状态。如果您在分配资源之前验证参数,攻击者的 finalize() 方法将只看到 null 引用。如果在调用超类构造函数之前拒绝无效参数,则根本没有最终处理。 - Holger
@Holger 0) 你正在处理的传统代码保证没有漏洞。 :-/ 是的,你必须积极地去阻止一个简单的构造函数出现问题,而且在我看来,一个能隐藏错误的复杂构造函数本身就是糟糕的代码气味,但有时你必须处理你已经得到的代码。 - Andrew Henle
1
@AndrewHenle 同时,这个过时警告已经被提升为“待删除”。(https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/lang/Object.html#finalize()) 因此,这个问题在未来会自动消失。 - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接