核心结论先行
在 Java 9 及更高版本中,finalize() 方法已被废弃(deprecated),从 Java 12 开始,它被移除,变为一个 private 方法。

在现代 Java 开发中,你不应该、也不需要手动调用 finalize(),它的设计初衷是为了在对象被垃圾回收器回收前,执行一些资源清理操作,但这种机制并不可靠,且已被更优越的替代方案所取代。
finalize() 的作用和问题
finalize() 的作用
finalize() 是 Object 类的一个方法,任何 Java 类都继承了这个方法,它的设计意图是:
当垃圾回收器确定不存在对该对象的任何更多引用时,由对象的垃圾回收器调用此方法,子类覆盖
finalize方法以配置系统资源或执行其他清理。
它就像一个“临终仪式”,在对象即将被“处决”(GC回收)前,给它一个机会进行自我清理。

finalize() 的严重问题
尽管初衷良好,但 finalize 机制存在许多致命缺陷,导致它极不可靠:
-
执行时机不确定:
finalize()的调用完全由垃圾回收器(GC)决定,你无法预测它何时会被执行,可能是在对象不再被引用后立即执行,也可能是在程序结束前,甚至永远不会执行(程序因内存不足而崩溃,或者内存足够大,GC 根本没有被触发)。- 这使得
finalize()不适合用于需要及时释放的资源,如数据库连接、文件句柄、网络套接字等。
-
性能开销大:
- 使用
finalize的对象,其回收过程会变得非常复杂和缓慢,GC 需要经历两个阶段:标记阶段 和 终结阶段。 - 在终结阶段,GC 会将对象标记为“待终结”,然后在一个新的线程中执行其
finalize()方法,这个过程会显著拖慢垃圾回收的效率,增加 GC 的停顿时间,影响整个应用的性能。
- 使用
-
执行顺序不可控:
(图片来源网络,侵删)- 如果对象 A 持有对象 B 的引用,且两个类都重写了
finalize(),GC 会以不确定的顺序调用它们的finalize()方法,这可能导致资源依赖关系错乱,引发难以排查的问题。
- 如果对象 A 持有对象 B 的引用,且两个类都重写了
-
可能导致对象复活:
- 在
finalize()方法内部,如果将对象的this引赋值给一个静态变量或其他可达引用,那么这个对象就被“复活”了,垃圾回收器会放弃回收它,这会使得finalize()被再次调用,形成死循环,逻辑非常混乱。
- 在
如何“手动”调用 finalize()?
虽然不推荐,但从技术层面看,你有两种方式可以“强制”执行 finalize() 的逻辑。
直接调用(强烈不推荐)
你可以像调用普通方法一样,直接在一个对象实例上调用 finalize()。
public class MyResource {
private String name;
public MyResource(String name) {
this.name = name;
System.out.println("资源 " + name + " 被创建");
}
@Override
protected void finalize() throws Throwable {
// 这是清理逻辑
System.out.println("资源 " + name + " 的 finalize() 被调用,正在清理...");
// ... 清理代码 ...
}
}
public class Main {
public static void main(String[] args) {
MyResource resource = new MyResource("手动调用");
// 直接调用 finalize()
// 注意:即使调用了,对象也依然存在,不会被回收
try {
resource.finalize();
} catch (Throwable t) {
t.printStackTrace();
}
System.out.println("手动调用 finalize() 后,对象依然存在: " + resource);
}
}
输出结果:
资源 手动调用 被创建
资源 手动调用 的 finalize() 被调用,正在清理...
手动调用 finalize() 后,对象依然存在: com.example.MyResource@15db9742
为什么这么做是错误的?
- 混淆了“清理”和“回收”:
finalize()是为 GC 回收服务的,你手动调用它,只是执行了一段清理代码,但对象本身仍然存活,占用内存,这完全违背了 GC 的自动管理原则。 - 可能被重复执行:如果这个对象后续真的被 GC 回收了,它的
finalize()会被 GC 再次调用,导致清理逻辑被执行两次,可能引发错误。
通过 System.runFinalization()(不推荐)
这个方法会建议 JVM 执行所有待终结对象的 finalize() 方法。
public class Main {
public static void main(String[] args) {
MyResource resource = new MyResource("通过runFinalization");
resource = null; // 断开引用,使其成为 GC 候选人
// 建议JVM执行所有待终结对象的finalize方法
System.runFinalization();
// 注意:这只是一个“建议”,JVM可以忽略它
// 并且它不会立即执行GC
}
}
为什么这么做是错误的?
- 仍然是建议,而非保证:
runFinalization()只是向 JVM 提交了一个请求,它不保证会立即执行,甚至不保证会执行。 - 性能问题:同样会触发复杂的终结流程,影响性能。
正确的资源管理方式(finalize 的替代方案)
既然 finalize 不可靠,我们应该使用哪些现代、可靠的方式来管理资源呢?
try-finally 块(最经典、最可靠)
这是最基础、最通用的方式,适用于任何需要释放资源的场景。
import java.io.FileInputStream;
import java.io.IOException;
public class TryFinallyExample {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("myfile.txt");
// 使用 fis 读取文件...
int data = fis.read();
System.out.println("读取到字节: " + data);
} catch (IOException e) {
System.err.println("文件读取出错: " + e.getMessage());
} finally {
// 确保资源一定会被关闭
if (fis != null) {
try {
fis.close();
System.out.println("文件流已关闭");
} catch (IOException e) {
System.err.println("关闭文件流时出错: " + e.getMessage());
}
}
}
}
}
优点:
- 100% 可靠:无论
try块中是否发生异常,finally块中的代码一定会被执行。 - 简单直观:所有 Java 开发者都熟悉。
try-with-resources(Java 7+,推荐)
这是 Java 7 引入的语法糖,是 try-finally 的现代化、更简洁的替代方案,它要求资源实现了 AutoCloseable 接口(Closeable 是其子接口)。
import java.io.FileInputStream;
import java.io.IOException;
public class TryWithResourcesExample {
public static void main(String[] args) {
// try-with-resources 语句
try (FileInputStream fis = new FileInputStream("myfile.txt")) {
// 使用 fis 读取文件...
int data = fis.read();
System.out.println("读取到字节: " + data);
} catch (IOException e) {
System.err.println("文件读取出错: " + e.getMessage());
}
// 不需要手动调用 fis.close()!
// try 块执行完毕后,JVM会自动调用 fis.close()
System.out.println("文件流已自动关闭");
}
}
优点:
- 代码简洁:消除了冗余的
finally块和null检查。 - 更安全:即使在
try块中发生异常,资源也会被正确关闭。 - 可以管理多个资源:用分号隔开即可。
try (FileInputStream fis = new FileInputStream("in.txt"); FileOutputStream fos = new FileOutputStream("out.txt")) { // ... }
Cleaner API(Java 9+,finalize 的现代替代品)
Cleaner 是 java.lang.ref 包下的一个类,它提供了一个更可靠、更可控的机制来执行清理操作,是 finalize 的官方替代品。
它的工作原理是:
- 创建一个
Cleaner实例。 - 为你的对象创建一个“清理操作”(一个实现了
Runnable接口的类)。 - 调用
cleaner.register(obj, runnable),将对象和清理操作关联起来。
当这个对象变成垃圾时,GC 会发现它关联了 Cleaner,并异步地执行注册的 Runnable 任务。
import java.lang.ref.Cleaner;
// 需要被清理的资源类
class MyResourceWithCleaner {
private final String name;
// 使用静态的Cleaner实例,通常由一个公共的Service提供
private static final Cleaner cleaner = Cleaner.create();
MyResourceWithCleaner(String name) {
this.name = name;
System.out.println("资源 " + name + " 被创建");
// 注册清理操作
cleaner.register(this, new MyResourceCleaner(name));
}
// 清理逻辑被封装在一个独立的Runnable中
private static class MyResourceCleaner implements Runnable {
private final String resourceName;
MyResourceCleaner(String name) {
this.resourceName = name;
}
@Override
public void run() {
// 这是真正的清理逻辑
System.out.println("资源 " + resourceName + " 被Cleaner异步清理");
}
}
}
public class CleanerExample {
public static void main(String[] args) throws InterruptedException {
MyResourceWithCleaner resource = new MyResourceWithCleaner("Cleaner示例");
resource = null; // 断开引用,使其成为GC的候选人
// 提示GC运行,以便看到清理效果
// 在真实应用中,你不需要这样做
System.gc();
// 给 Cleaner 一些时间来执行异步任务
Thread.sleep(500);
System.out.println("主线程结束");
}
}
输出结果:
资源 Cleaner示例 被创建
主线程结束
资源 Cleaner示例 被Cleaner异步清理
优点:
- 更可靠:比
finalize更接近 GC 的行为,但避免了finalize的许多问题。 - 性能更好:终结过程比
finalize更高效。 - 不会复活对象:
Cleaner的任务执行是单向的,不会让对象复活。
缺点:
- 仍然是异步的:执行时机依然不确定。
- 不适合需要立即释放的资源:与
finalize一样,不适合数据库连接等需要立即归还的资源。
| 特性 | finalize() (已废弃) |
try-finally |
try-with-resources (推荐) |
Cleaner API |
|---|---|---|---|---|
| 可靠性 | 极低,时机不确定 | 极高,一定会执行 | 极高,一定会执行 | 较高,但时机不确定 |
| 执行时机 | GC 决定,可能永不执行 | 立即执行 | 立即执行 | GC 决定,异步执行 |
| 性能 | 差,拖慢 GC | 好 | 好 | 较好 |
| 代码简洁性 | N/A | 冗长 | 简洁 | 较为复杂 |
| 适用场景 | 不推荐在任何场景使用 | 所有需要手动释放资源的场景 | 所有实现了 AutoCloseable 的资源 |
本地资源(如文件句柄、内存块)的清理 |
| Java 版本 | 1 - 8 (已移除) | 所有版本 | Java 7+ | Java 9+ |
最终建议:
- 对于文件、网络流、数据库连接等:始终使用
try-with-resources,这是最现代、最安全、最简洁的方式。 - 对于任何需要手动释放的资源:使用
try-finally块作为后备方案。 - 绝对不要:依赖
finalize()或手动调用它。 - 仅在特定高级场景(如管理 JNI 调用分配的本地内存)下,可以考虑使用
CleanerAPI,并充分理解其异步执行的特性。
