Java中替代finalize()方法的方式

30

Object.finalize() 在Java 9中已被弃用,我认为我理解了原因,但我不知道如何替换它。

我有个实用类称为 Configuration,它基本上拥有应用程序中的所有东西,并持续存在于应用程序的整个生命周期。其中一个服务是记录日志:在第一次请求记录消息时,会创建一个记录器(由于各种传统原因,我的记录器是自己的 Logger 而不是标准的记录器),并将引用保存在 Configuration 对象的字段中,在应用程序终止时,无论是正常还是异常终止,都要释放任何记录器持有的资源(由于我的库的用户可以提供自己的实现,因此它是一个黑盒子)。

目前使用的方式是通过 Configuration.finalize() 方法调用 logger.close() 实现的。

我应该做些什么?


2
FYI - shmosel
4
因为应用程序终止时未进行最终化,所以您以前的方法从未起作用,而您却从未意识到。那么,您确定需要这种清理吗? - Holger
4个回答

17

Java 9引入了Cleaner和Cleanable实用类,它们负责将幽灵引用与队列和清理线程连接起来。

虽然这使您可以将执行事后清理的观察者与所有权对象分开,但有关由GC触发的资源管理的所有警告仍适用,即仍然更喜欢依赖AutoClosable和try-with-resources块来管理资源的生命周期,而不是垃圾回收器。


2
不错的例子 http://www.enyo.de/fw/notes/java-finalization-revisited.html - expert
由于这是我们最终实施的方案,因此对此答案的接受有些迟了。(我们在资源清理方面遇到的最大问题是,在某些情况下,我们将可关闭的流作为API调用的结果传递,而客户端既没有完全读取流,也没有关闭它。) - Michael Kay

9

谢谢。引用的文章是我见过的最易读的幽灵引用解释;在阅读周围内容后,有两件事是清楚的:(a)人们发现很难解释它们,(b)为了使它们工作,您必须用大约6个单独的类的复杂结构替换一个一行的finalize()方法。(可以假设如果github示例被认为是“简单”的话,那么这并不容易。)这并不鼓舞人心,但还是谢谢! - Michael Kay
1
最好的方法是不要将对象生命周期与垃圾回收耦合在一起。使用特殊引用(finalizers、phantom等)应该被视为最后的选择。不幸的是,有时你必须走这条艰难的路。 - Alexey Ragozin

4
您可以将线程作为关闭挂钩添加到Runtime中。具体实现可参考此文档
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    // cleanup code
}));

这是在虚拟机终止时调用的,因此它应该是您特定情况下finalize的很好替代品。

它在许多情况下都可以工作。但是,在某些情况下,虚拟机将在运行完我的应用程序后继续执行其他操作。当然,我不希望在虚拟机完成之前将配置对象锁定在内存中。 - Michael Kay

2

我的看法是,你的应用程序不应该告诉日志记录器去清理它自己的混乱。日志记录器本身可以并且应该这样做(与IO流或数据库连接不同),因为它应该长时间存活。

但是你已经提供了logger.close()...那么我建议如下:

  • 确保close是幂等的,即第二次关闭日志记录器是无操作的。
  • 同时使用Runtime#addShutdownHookPhantomReference,让它们都调用logger.close(),这样当JVM终止或您的应用程序被GC时都会调用它。

使用Runtime#addShutdownHook的建议是否解决了Michael在对Lothar的回答的评论中指出的配置对象被锁定在内存中的问题。如何解决这个问题?谢谢。 - KnockingHeads

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