Java异常性能损耗主因是throw时fillInStackTrace()采集栈轨迹(耗时1–5微秒),而非try-catch语法本身;热路径抛异常会严重降低吞吐量,自定义异常可重写该方法优化。

Java 中异常处理不当主要不是因为 try-catch 语法本身拖慢程序,而是在不该抛异常的地方抛了异常,且频繁发生——性能损耗真正发生在 throw 那一刻,而不是 catch 块里。
异常开销的核心来源是栈轨迹采集
每次执行 throw new Exception(),JVM 必须调用 fillInStackTrace():
- 同步遍历当前线程全部调用栈帧
- 记录类名、方法名、文件名、行号等信息
- 这个过程耗时达 1–5 微秒(栈越深越慢),比普通对象创建慢 10–100 倍
- 即使你立刻
catch并忽略,开销也已不可逆地发生
热路径上抛异常会直接拉垮吞吐量
在高频执行的代码段(如网络解析、循环处理、实时计算)中触发异常,后果严重:
- 一次
Integer.parseInt("abc")抛出NumberFormatException,比用Character.isDigit()预检 +if分支慢 上千倍(纳秒级 vs 微秒级) - 循环内每轮都
new ParseException(),相当于为每一项额外增加一次同步栈采集,QPS 可能下降 30% 以上 - JIT 编译器无法优化异常路径,因为它被设计为“极低概率事件”,一旦变成常态,就失去优化前提
自定义异常若未优化,放大开销
默认构造的自定义异常会完整执行 fillInStackTrace():
立即学习“Java免费学习笔记(深入)”;
- 若该异常只用于状态标识(如协议解析失败、参数校验不通过),并不需要堆栈信息
- 可重写
fillInStackTrace()直接return this,跳过采集逻辑 - 或在构造函数中调用
super(null),避免初始化stackTrace字段
try-catch 块本身几乎无成本,但滥用掩盖问题
- JVM 对
try块的字节码处理已高度优化;无异常抛出时,性能与普通代码基本一致 - 真正危险的是:
- 把整段业务逻辑包进一个大
try,导致本该快速失败的错误被延迟暴露 -
catch块里做日志序列化、远程调用、加锁等阻塞操作,把单次异常延迟放大成服务抖动 - 捕获
Exception或Throwable后不做区分,让NullPointerException和IOException走同一套重试逻辑,违背错误语义
- 把整段业务逻辑包进一个大
不复杂但容易忽略。



















