会。现代Java编译器(Java 6起)将finally块字节码复制到try正常结束、每个catch末尾、所有显式退出点及异常抛出前,以替代已废弃的jsr/ret指令,确保语义正确且兼容JIT优化。

会。现代 Java 编译器(Java 6 及以后)确实会在字节码中为 finally 块生成多份代码副本,这是实现“无论怎样都执行”语义的关键机制。
为什么需要复制代码而不是跳转
早期 JVM(Java 5 及以前)用 jsr/ret 指令模拟子程序调用,但存在栈帧管理复杂、调试困难、与现代 JIT 优化不兼容等问题。JVM 规范在 Java 6 中正式弃用 jsr/ret,改由编译器主动将 finally 块的字节码复制到每个可能的退出路径上。
复制发生在哪些位置
编译器会把 finally 块内容插入到以下所有控制流出口前:
- try 块正常结束处(无异常、无 return/break/continue)
- 每个 catch 块末尾(无论是否含 return)
- try 或 catch 中显式 return / throw / break / continue 的位置之后
- 异常未被捕获时,方法即将向上抛出异常前
一个直观的例子
如下源码:
private static int test() {int a = 1;
try {
a = 3 / 0;
} catch (Exception e) {
System.out.println("error");
return a;
} finally {
System.out.println("finally");
}
return a;
}
反编译后可见:System.out.println("finally") 对应的字节码被复制了两次——一次插在 catch 中 return 前,另一次插在方法末尾的 return 前。即使 catch 中有 return,JVM 也会先执行那里的 finally 副本,再真正返回。
这对开发者意味着什么
理解这个机制能帮你避开几个典型陷阱:
- finally 中的 return 会覆盖 try/catch 中的 return 值(因为它是最后执行的)
- finally 不适合放 return 或 throw,否则会吞掉原始异常或返回值
- 调试时看到“同一行代码执行多次”是正常的,不是逻辑错误,而是编译器生成的多个副本
- 性能上无额外调用开销,但字节码体积略增,不过对运行时影响可忽略

















