Java 为什么弃用 finalize() ?

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值