finally并非绝对执行,仅在try/catch已启动且JVM未强制终止时运行;其保障源于编译器将finally代码复制插入各出口路径,而非JVM特殊支持。

finally 块在绝大多数情况下都会执行,但“一定”要加前提:只要对应的 try 或 catch 块曾开始执行,且 JVM 未被强制终止,finally 就会运行。它不是魔法,而是由 JVM 字节码层面的异常表(exception table)和 finally 编译重写机制共同保障的。理解其执行边界,比死记“一定执行”更重要。
finally 的执行由编译器保障,而非 JVM 特殊照顾
Java 编译器(javac)在编译含 finally 的代码时,会将 finally 中的语句**复制多份**,分别插入到每个可能的控制流出口之后:
- try 块正常结束 → 插入一份 finally
- 每个 catch 块末尾 → 各插入一份 finally
- try/catch 内发生 return → 在 return 表达式求值后、真正返回前插入 finally
也就是说,你写的 1 个 finally,在字节码里可能变成 3–5 个重复片段。这解释了为何 return 都拦不住它——因为 return 还没真正发生,finally 已被“提前安排”在返回路径上。
finally 不执行的 4 种真实例外情况
以下情形下,finally 永远不会被执行:
立即学习“Java免费学习笔记(深入)”;
-
JVM 被强制退出:如调用
System.exit(0)、Runtime.getRuntime().halt(),进程直接终止,所有后续字节码(包括 finally)被跳过 -
线程被杀死:如调用已废弃的
Thread.stop()(不推荐且危险),线程立即死亡,不走任何清理逻辑 - JVM 崩溃或断电:底层系统级故障,无任何 Java 代码能响应
-
无限循环或阻塞卡死在 try/catch 中:例如
while(true){}或Object.wait()未唤醒,程序根本走不到 finally 所在位置
finally 中的 return 会覆盖 try/catch 的返回值
这是易错点:如果 finally 里有 return,它会直接结束方法,丢弃 try 或 catch 中已准备好的返回值。
public static int getValue() {
try {
return 1;
} finally {
return 2; // ✅ 实际返回 2;try 中的 1 被彻底忽略
}
}
同理,若 finally 抛出异常,也会吞掉 try/catch 中的异常或返回动作。因此:避免在 finally 中 return 或 throw,仅用于资源释放(如 close())。
资源释放推荐用 try-with-resources,而非手动 finally
对于实现了 AutoCloseable 的资源(如 FileInputStream、Connection),优先使用 try-with-resources:
try (FileInputStream fis = new FileInputStream("a.txt")) {
// 使用 fis
} // ✅ 自动调用 fis.close(),无需手写 finally
它本质是编译器自动生成 finally 调用 close(),更安全、简洁,还能正确处理多个资源关闭时的异常压制(suppressed exception)。










