finally块并非一定执行,system.exit()、runtime.getruntime().halt()、线程强制中断、死循环、jvm崩溃等场景下均不执行;return语句会先保存值再执行finally,其内return可覆盖原返回值。

finally 块真的“一定执行”吗?
不,finally 块**绝大多数情况下会执行**,但存在明确的、可复现的例外。它不是魔法,而是 JVM 在字节码层面通过异常表(exception table)插入的强制跳转逻辑——只要控制流能正常退出 try/catch 结构,finally 就会被调用。
-
System.exit()被调用:JVM 立即终止,finally不执行 -
Runtime.getRuntime().halt():绕过所有 shutdown hook 和finally - 线程被强制中断(如
Thread.stop(),已废弃但仍有影响) - try 或 catch 中陷入死循环(
while(true) {}),程序卡住,根本走不到finally - JVM 崩溃、OOM 导致进程被操作系统 kill -9、断电等底层故障
Java 中 return 语句和 finally 的执行顺序
在 try 或 catch 中写 return,JVM 并不会立刻返回——它会先保存返回值,再跳入 finally 执行;finally 执行完,才真正返回。这意味着:finally 里改了变量、甚至写了 return,都可能覆盖原返回值。
- 如果
try返回1,finally中写return 2;,方法最终返回2 - 如果
finally没有return,但修改了引用对象的字段(如obj.value = 99;),调用方看到的就是修改后的状态 - 基本类型返回值在
finally中无法被“修改”,因为 JVM 已缓存副本;但包装类或对象引用仍可能被间接改变
public static int test() {
try {
return 1;
} finally {
return 2; // 这行会覆盖 try 中的 return 1
}
}
资源释放场景下 finally 的典型写法
关闭 FileInputStream、Connection、ResultSet 等资源时,finally 是传统方案,但要注意空指针和嵌套异常。
- 必须判空:资源变量初始化为
null,finally中先检查是否非空再关闭 - 关闭操作本身可能抛出异常(如
IOException),要单独try-catch,避免掩盖原始异常 - 多个资源需分别关闭,且后关闭的不能因前一个失败而跳过(例如先关
ResultSet,再关Statement,最后关Connection) - 现代写法更推荐
try-with-resources,自动处理关闭和抑制异常,finally仅作兜底或特殊清理
finally 中抛异常会怎样?
如果 finally 块中抛出未捕获的异常,它会完全取代 try/catch 中原本要抛出或返回的结果——哪怕 try 里已经 throw new NullPointerException(),只要 finally 接着 throw new RuntimeException(),调用方收到的就只是后者。
- 原始异常不会丢失,JVM 会将其作为 suppressed exception 附加到新异常上(可通过
getSuppressed()获取) - 若
finally抛异常 +try也抛异常,后者被压制,前者主导栈轨迹 - 实践中应避免在
finally中主动throw,尤其不要在关闭资源时因忽略catch而让IOException泄露出来
真正难的不是记住“finally 总会执行”,而是理解它在字节码中的插入时机、与 return 的协作机制、以及多层异常压制下的可观测性。这些细节一旦出错,问题往往藏得深、复现难、日志还被覆盖掉。










