Throwable 的核心是异常对象构建与抛出分发的“双轨驱动机制”:前者通过 fillInStackTrace() 快照栈帧、组装消息和因果链,后者依赖字节码 ExceptionTable 匹配跳转;二者协同完成异常处理。

要真正理解 Throwable 的核心源码和 JVM 异常的“双轨驱动机制”,关键在于看清两条并行路径:一条是异常对象的构建与封装路径(堆栈填充、消息组装、因果链建立),另一条是异常抛出后的查找与分发路径(字节码异常表匹配、栈帧回溯、处理器跳转)。这两条路径在运行时协同工作,缺一不可。
Throwable 对象的初始化不是简单 new,而是带状态快照的构造
Throwable 的构造方法看似普通,实则暗含 JVM 深度介入:
-
fillInStackTrace() 是 native 方法,由 JVM 在对象创建瞬间执行,负责采集当前线程的完整调用栈,并存入
stackTrace数组;它不依赖 Java 层代码,也无法被子类覆盖 -
backtrace字段为transient,说明它不参与序列化——因为栈信息属于运行时上下文,跨进程/网络传输无意义 -
cause默认指向自身(this),只有显式传入非空Throwable才构成异常链;这种设计让嵌套异常(如 SQLException 包裹 IOException)可逐层追溯根源 -
detailMessage是纯字符串,不自动包含堆栈,所以getMessage()只返回它,而printStackTrace()才触发完整上下文输出
异常传播不是靠“抛”动作本身,而是靠字节码异常表驱动跳转
try-catch 不是语法糖,它在编译后会生成真实的数据结构——ExceptionTable,位于方法的字节码属性中:
- 每条表项定义了监控范围:
start_pc到end_pc,即哪些字节码指令受保护 - 指定捕获类型:
catch_type指向常量池中的异常类符号引用(如java/lang/ArithmeticException) - 命中后跳转地址:
handler_pc直接指向 catch 块编译后的第一条指令偏移量 - JVM 在 throw 时并不遍历所有 try 块,而是按栈帧从顶向下查当前方法的 ExceptionTable,匹配成功即跳转,否则弹出栈帧继续查上层——这是性能关键
双轨交汇点:异常对象创建完成 + 异常表匹配成功 = 安全落地
一个异常能被正确捕获,必须同时满足两个条件:
- Throwable 实例已完整构建:栈帧快照写入、消息填充完毕、因果关系明确
- 当前执行点落在某 try 块的
start_pc~end_pc范围内,且抛出类型与表中catch_type兼容(如抛NullPointerException,表中为RuntimeException也匹配) - 若任一条件失败(如栈溢出导致无法分配 stackTrace 数组,或抛出异常时不在任何 try 范围内),JVM 就终止线程并交由未捕获处理器处理
虚拟线程下双轨机制不变,但传播路径更隐蔽
虚拟线程虽轻量,但异常仍走相同双轨:
- 其
fillInStackTrace()仍采集逻辑调用栈(非载体线程物理栈),确保业务上下文不失真 - 字节码 ExceptionTable 依然存在,JVM 虚拟线程调度器会在 carrier thread 上模拟栈帧回溯行为
- 区别在于:未捕获异常不会杀死 carrier thread,而是触发
UncaughtExceptionHandler后,该虚拟线程直接结束,载体线程继续调度其他任务 - 因此调试时需主动设置线程名和日志上下文,否则
printStackTrace()显示的线程名是自动生成的无意义字符串


















