异常表是JVM在字节码中为每个含try-catch/finally的方法静态构建的跳转索引表,含from、to、target、type四字段,异常发生时JVM依此表直接定位handler,实现高效非线性执行。

Java 的 try-catch-finally 看似线性书写,实际执行是靠 JVM 的异常表(exception table)驱动的非线性跳转。它不是靠运行时“扫描代码”来匹配 catch,而是编译期就静态构建好一张跳转索引表,JVM 在抛出异常的瞬间查表定位目标 handler,效率极高。
exception table 是什么?
每个含 try-catch 或 finally 的方法,在编译为 class 文件后,都会附带一张异常表。它不是 Java 源码里的结构,而是字节码层面的元数据,可通过 javap -c -v 查看。表中每条记录包含四个字段:
- from 和 to:字节码索引范围(左闭右开),表示该 handler 对应的 try 块覆盖的指令区间
- target:catch 或 finally 入口的字节码索引,即跳转目标位置
-
type:能捕获的异常类的常量池索引(如
java/lang/Exception),值为 0 表示可捕获所有异常(对应catch(Throwable)或 finally)
try-catch 的跳转如何发生?
当某条指令(比如 athrow 或隐式空指针触发)抛出异常时,JVM 不会逐行回溯调用栈找 catch,而是做两件事:
- 检查当前方法的 exception table,找出所有满足
from ≤ 异常发生处pc 且type与抛出异常类型兼容的记录 - 取第一条匹配记录,将程序计数器(PC)直接设为它的
target,开始执行 catch 块字节码
这意味着:没有匹配的表项 → 异常未被捕获,向上抛给调用方;多条匹配?只取第一条(按编译顺序排列)。
finally 为何“一定执行”?
finally 不依赖异常是否发生,它的保障机制更底层:编译器会把 finally 块的字节码复制多份,分别插入到每个可能的出口路径末尾 —— 包括 try 正常结束、每个 catch 执行完、甚至 try/catch 中的 return 之前。
例如,一个 try { return x; } catch {...} finally { close(); },编译后相当于:
- try 分支末尾插入:
close(); return x; - catch 分支末尾插入:
close(); return y; - 若 try 中抛异常但无匹配 catch,JVM 查表跳到 finally 复制体,再重新抛出
这种“代码复制”策略,让 finally 的执行不依赖异常处理流程,而是嵌入在每条控制流里。
return 与 finally 的冲突怎么解决?
当 try 或 catch 中有 return,JVM 并不会立刻退出方法,而是:
- 先将待返回值暂存到操作数栈或局部变量槽
- 再跳转执行 finally 块(此时 finally 可读写该变量,但不会影响已暂存的返回值)
- finally 执行完,才从暂存位置取出原值返回
- 除非 finally 自己也写
return或抛异常——这会覆盖之前的返回决定,成为最终结果
这就是为什么 finally 里写 return 是高危操作:它会静默吞掉 try/catch 的返回值或异常。











