0 概述
- 在 Java 中,
finalize()
方法曾是对象生命周期的一部分,允许在垃圾收集器准备回收对象之前执行一些清理操作。然而,随着时间的推移,finalize()
方法逐渐暴露出一些问题,包括性能开销、不确定的行为、死锁风险以及增加了垃圾回收的复杂性。
1 为什么弃用
-
性能问题:该方法的执行会增加垃圾回收的停顿时间,因为 JVM 必须等待对象的
finalize()
方法执行完毕才能回收对象。这对于需要低延迟和高吞吐量的应用来说可能会产生性能问题 -
不确定性:执行时间是不确定的,因为它依赖于垃圾回收器的运行时机,而垃圾回收器的运行时机是不可预测的。这导致依赖
finalize()
进行资源清理的代码很难编写和调试 -
死锁风险:如果
finalize()
方法在执行过程中访问了其他对象,而这些对象又恰好正在被垃圾回收,那么就可能发生死锁 -
安全问题:可以被恶意代码利用来破坏系统的安全性。例如,恶意代码可以覆盖对象的
finalize()
方法,在对象被垃圾回收时执行恶意操作 -
因此,基于以上原因,它在 Java 9 中被标记为弃用(deprecated)
2 替代方案
- 既然
finalize()
方法已被弃用,那么有没有更好的替代呢? - 答案当然是有的:
try-with-resources
语句java.lang.ref.Cleaner
类
3 try-catch-resources 演示
- Java 7 引入了
try-with-resources
语句,它自动关闭实现了 AutoCloseable 接口的资源。这是一个优雅且安全的方式来管理资源,如文件、网络连接。 - 以下是一个 demo 演示
import cn.hutool.core.io.IoUtil; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; /** * try-catch-resources语句 * @Author Jasper * @Time 2024/2/26 * @公众号:EzCoding */ public class TryResources { public static void main(String[] args) { File file = new File("./file.txt"); try (InputStream is = new FileInputStream(file)) { IoUtil.readBytes(is); } catch (Exception e) { e.printStackTrace(); } } }
- InputStream 实现了 AutoCloseable 接口,因此它可以在 try 结束时自动关闭,无需显式调用 close() 方法,源码如下:
public abstract class InputStream implements Closeable { // 只展示结构,具体内容省略 } public interface Closeable extends AutoCloseable { /** * Closes this stream and releases any system resources associated * with it. If the stream is already closed then invoking this * method has no effect. * * <p> As noted in {@link AutoCloseable#close()}, cases where the * close may fail require careful attention. It is strongly advised * to relinquish the underlying resources and to internally * <em>mark</em> the {@code Closeable} as closed, prior to throwing * the {@code IOException}. * * @throws IOException if an I/O error occurs */ public void close() throws IOException; }
4 Cleaner 演示
- Java 9 引入了 Cleaner 类作为替代方案。允许注册一个回调,当对象变得幻象可达(phantom reachable)时,这个回调会被执行。与 finalize() 不同,Cleaner 不会延迟对象的回收,并且它的执行是异步的。
- 以下是 demo 代码
import java.lang.ref.Cleaner; import java.lang.ref.Cleaner.Cleanable; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; /** * @Author Jasper * @Time 2024/2/26 * @公众号:EzCoding */ public class CleanerDemo { // 模拟需要清理资源的类 static class ManagedResource { private final AtomicBoolean isClosed = new AtomicBoolean(false); public void close() { if (isClosed.compareAndSet(false, true)) { System.out.println("资源已被清理。"); } } public boolean isClosed() { return isClosed.get(); } } // 模拟持有需要清理资源的类,并使用 Cleaner 进行清理 static class ResourceHolder implements AutoCloseable { private final ManagedResource resource = new ManagedResource(); private final Cleanable cleanable; public ResourceHolder() { this.cleanable = Cleaner.create().register(Executors.defaultThreadFactory(), () -> { System.out.println("Cleaner 正在执行清理..."); resource.close(); }); } @Override public void close() { // 手动触发清理 cleanable.clean(); } public boolean isResourceClosed() { return resource.isClosed(); } } public static void main(String[] args) throws InterruptedException { ResourceHolder holder = new ResourceHolder(); // 这里是使用资源逻辑 // 模拟应用程序逻辑结束,释放资源 holder.close(); // 检查资源是否已被清理 System.out.println("资源是否已关闭: " + holder.isResourceClosed()); // 强制进行垃圾回收以演示 Cleaner 的工作 System.gc(); // 等待一段时间以便 Cleaner 有机会执行(这只是示例,实际执行时间不确定) Thread.sleep(1000); } }
- 输出结果
Cleaner 正在执行清理... 资源已被清理。 资源是否已关闭: true
补充说明:在上述例子中,手动调用了 holder.close(),因此 Cleaner 的回调实际上不会被触发,因为资源已经在 Cleaner 有机会介入之前被清理了。如果注释掉 holder.close() 的调用,那么当 holder 对象变得幻象可达时,Cleaner 的回调可能会被触发(但这仍是不确定的)
5 总结
- 依赖垃圾回收器和 Cleaner 进行资源管理一般不是最佳实践。Cleaner 主要用于在对象被垃圾回收时执行一些额外的、不是很关键的清理任务
- 应优先使用 try-with-resources 语句且资源实现 AutoCloseable 接口,确保在不再需要资源时立即释放
- 创作不易,感谢阅读,若您喜欢这篇文章,不妨传承这份知识的力量,点个赞关注我吧~
- 微信公众号:EzCoding