条目8:避免使用终结方法和清理方法
什么是终结方法(finalizer)和清理方法(cleaner)?
终结方法(finalizer):
- 是
Object
类的一个方法:protected void finalize()
,可以由子类重写。(Java9开始已经弃用,但是Java类库仍在使用) - 它在垃圾收集器(Garbage Collector)将对象回收之前调用,用来释放资源或执行清理操作。
清理方法(cleaner):
- 是 Java 9 引入的
java.lang.ref.Cleaner
类,作为更现代的替代方案。 - 它提供了一种注册清理任务的机制,当对象被垃圾回收时,清理任务会被执行。
为什么要避免使用终结方法和清理方法?
-
性能问题
-
垃圾回收性能影响:
使用终结方法的对象会变得更昂贵,GC需要额外跟踪这些对象并保证调用其
finalize
方法,这可能显著影响GC的性能。 -
不确定性:
终结方法的调用时间由垃圾回收器决定,可能导致资源回收延迟。例如,文件句柄或数据库连接可能长时间未释放,影响系统性能。
-
-
不可预测性
-
不保证被调用:
虽然 Java 的垃圾回收器会尝试调用终结方法,但这不是强制的。如果 JVM 退出,未调用的终结方法将永远不会执行。
-
调用顺序不明确:
如果多个对象之间有依赖关系,终结方法的调用顺序无法保证,可能引发程序错误。
-
-
引发潜在的安全问题
-
终结方法可能被恶意子类重写,构造破坏性的行为(例如,复活对象)。
-
攻击者可以利用终结方法重新获取权限或篡改系统状态。
-
-
可以用更好的方式代替
- 许多终结方法的应用场景(如释放非内存资源)可以通过
try-with-resources
或显式的close()
方法更优雅、安全地处理。
- 许多终结方法的应用场景(如释放非内存资源)可以通过
推荐代替方式
-
使用
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"); } } }
-
显式关闭资源,对于需要显式管理的资源(例如文件、数据库连接),确保在使用完后调用
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(); } } }
-
使用
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()); // 后续操作 } }
什么时候适合使用终结方法或清理方法
-
作为安全网,终结方法或清理方法可以作为最后的“安全网”,用于检测开发者的疏忽,例如,可以在终结方法中记录未被正确关闭的资源以供调试。
@Override protected void finalize() throws Throwable { try { if (resourceNotClosed) { System.err.println("Resource not closed properly!"); } } finally { super.finalize(); } }
-
清理本地代码分配的内存,监控或记录对象的生命周期。