异常表是jvm查找catch块的唯一依据,含from、to、target、type四字段;异常发生时按pc匹配[from, to)范围及type类型(含子类),失败则向上抛,type=0表示finally或捕获所有异常。

异常表(Exception Table)是JVM查找catch块的唯一依据
JVM不靠扫描字节码或动态解析来定位catch,而是严格依赖每个方法附带的异常表。这张表在类加载时就已固定,运行时只做“查表+匹配”两步操作。
每个异常表条目含四个字段:from、to、target、type。当异常抛出时,JVM用当前PC寄存器值去比对所有[from, to)区间;命中后,再检查抛出异常是否为type指向类或其子类。
- 如果匹配失败,JVM立刻弹出当前栈帧,到调用者方法的异常表中重试
-
type为0(即any)表示捕获所有异常,finally块的异常表条目就属于这类 - 多个
catch块会生成多条表项,顺序从上到下——所以更具体的异常类型必须写在前面
finally块没有独立执行路径,只是字节码复制的结果
finally看上去“总会执行”,但JVM本身并无finally指令。OpenJDK 6之后,javac编译器直接把finally里的字节码复制到所有可能出口:正常返回前、每个catch末尾、甚至异常未被捕获而准备向上抛出的位置。
例如这段代码:
Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
public int test() {
try {
return 1;
} finally {
cleanup();
}
}
反编译后能看到cleanup()调用出现在两个地方:一次在ireturn之前,另一次在异常处理路径的astore_2之后。这不是JVM保证“绝对执行”,而是编译器提前铺好了所有退路。
- 若
try里有return,返回值会被暂存到局部变量表,等finally执行完再取出来ireturn - 若
finally里也有return,它会覆盖try中的返回值——因为那已是另一份字节码副本了 - 这种复制策略让
finally逻辑天然绕过异常传播链,但也带来冗余和调试困惑
异常对象创建开销远大于try-catch结构本身
try和catch关键字本身几乎零成本:无异常时,JVM只是维护一张静态异常表,不触发任何额外逻辑。真正昂贵的是throw那一刻——要构造Throwable实例、填充完整堆栈跟踪(fillInStackTrace())、触发GC压力。
- 频繁抛出异常(如用
NumberFormatException做字符串校验)会导致显著性能下降 - 某些场景可禁用堆栈跟踪(重写
fillInStackTrace()返回this),但会丢失关键诊断信息 -
try-with-resources虽依赖Throwable.addSuppressed(),但仅在资源关闭也抛异常时才触发二次开销
OutOfMemoryError这类Error不会走异常表流程
异常表只对Throwable的子类生效,而Error虽继承自Throwable,JVM对其处理是特殊通道:一旦触发(如堆内存耗尽),JVM直接终止线程或整个进程,不查找异常表、不执行finally、甚至不保证shutdown hook能跑完。
-
OutOfMemoryError发生时,finally块大概率不会执行——因为连分配Throwable对象的内存都没有了 -
StackOverflowError同理,栈空间已满,连压入catch块的栈帧都不可能 - 不要试图用
catch(Error)兜底,这既不可靠,也违背JVM设计意图
catch”,而是理解哪些异常能被表捕获、哪些已被JVM截断、哪些根本来不及进表——这些边界,往往在OOM或栈溢出时才突然暴露。Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










