Bootstrap

读《Effective Java》笔记 - 条目8

条目8:避免使用终结方法和清理方法

什么是终结方法(finalizer)和清理方法(cleaner)?

终结方法(finalizer)

  • Object 类的一个方法:protected void finalize(),可以由子类重写。(Java9开始已经弃用,但是Java类库仍在使用)
  • 它在垃圾收集器(Garbage Collector)将对象回收之前调用,用来释放资源或执行清理操作。

清理方法(cleaner)

  • 是 Java 9 引入的 java.lang.ref.Cleaner 类,作为更现代的替代方案。
  • 它提供了一种注册清理任务的机制,当对象被垃圾回收时,清理任务会被执行。

为什么要避免使用终结方法和清理方法?

  1. 性能问题

    • 垃圾回收性能影响

      使用终结方法的对象会变得更昂贵,GC需要额外跟踪这些对象并保证调用其 finalize 方法,这可能显著影响GC的性能。

    • 不确定性

      终结方法的调用时间由垃圾回收器决定,可能导致资源回收延迟。例如,文件句柄或数据库连接可能长时间未释放,影响系统性能。

  2. 不可预测性

    • 不保证被调用

      虽然 Java 的垃圾回收器会尝试调用终结方法,但这不是强制的。如果 JVM 退出,未调用的终结方法将永远不会执行。

    • 调用顺序不明确

      如果多个对象之间有依赖关系,终结方法的调用顺序无法保证,可能引发程序错误。

  3. 引发潜在的安全问题

    • 终结方法可能被恶意子类重写,构造破坏性的行为(例如,复活对象)。

    • 攻击者可以利用终结方法重新获取权限或篡改系统状态。

  4. 可以用更好的方式代替

    • 许多终结方法的应用场景(如释放非内存资源)可以通过 try-with-resources 或显式的 close() 方法更优雅、安全地处理。

推荐代替方式

  1. 使用 try-with-resources,只需要实现 AutoCloseable 接口

    class Resource implements AutoCloseable {
        @Override
        public void close() {
            System.out.println("Resource closed");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            try (Resource resource = new Resource()) {
                System.out.println("Using resource");
            }
        }
    }
    
  2. 显式关闭资源,对于需要显式管理的资源(例如文件、数据库连接),确保在使用完后调用 close() 方法

    FileInputStream fileInputStream = null;
    try {
        fileInputStream = new FileInputStream("file.txt");
        // 使用文件
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (fileInputStream != null) {
            try {
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  3. 使用 Cleaner它也继承了终结方法的许多缺点,因此只在特殊情况下使用。

    import java.lang.ref.Cleaner;
    
    public class CleanerExample {
        private static final Cleaner cleaner = Cleaner.create();
    
        static class Resource implements Runnable {
            @Override
            public void run() {
                System.out.println("Cleaning up resource...");
            }
        }
    
        public static void main(String[] args) {
            Object obj = new Object();
            cleaner.register(obj, new Resource());
            // 后续操作
        }
    }
    

什么时候适合使用终结方法或清理方法

  1. 作为安全网,终结方法或清理方法可以作为最后的“安全网”,用于检测开发者的疏忽,例如,可以在终结方法中记录未被正确关闭的资源以供调试。

    @Override
    protected void finalize() throws Throwable {
        try {
            if (resourceNotClosed) {
                System.err.println("Resource not closed properly!");
            }
        } finally {
            super.finalize();
        }
    }
    
  2. 清理本地代码分配的内存,监控或记录对象的生命周期。

;