try-catch本身无异常时开销极低,因jvm仅通过静态异常表记录信息且不执行匹配;真正高成本在于throw瞬间——需分配对象、构建栈轨迹(占60%以上)、栈展开与异常表扫描,实测抛异常比空catch慢260倍。

不需要盲目减少颗粒度,但必须严格限制作用范围——只包裹真正可能抛异常的最小语句,其余逻辑一律剥离。
不抛异常时,try-catch 本身开销极低
现代 JVM(如 HotSpot)和 .NET 运行时将 try 块编译为字节码中的异常表(exception table),不插入运行时检查指令。只要没触发 throw,CPU 就按正常路径执行,JIT 编译器还会对热点代码做深度优化(如内联、去虚拟化),使 try-catch 的存在几乎不可见。
- 实测:百万次空 try-catch 循环与纯计算循环耗时差通常在 0.1ms 级别
- 关键点在于“不抛异常”,而非“有没有 try”
真正昂贵的是 throw 那一瞬
一次异常抛出要同步完成:分配异常对象、调用 fillInStackTrace()(遍历整个调用栈生成 trace)、栈展开(stack unwinding)、查找匹配 catch 块。这个过程耗时可达普通方法调用的百倍以上,且随调用深度陡增。
- Spring Boot 接口中常见 30 层调用栈 → 每次 fillInStackTrace() 至少 30 次字符串构造 + 数组写入
- 若把异常当控制流用(如用 NumberFormatException 判断字符串是否为数字),高频抛出会直接拖垮吞吐量
颗粒度不是越小越好,而是越准越好
“细粒度”不等于“每行都套 try”,而是精准定位唯一风险点。错误做法是把日志、赋值、循环全包进去;正确做法是只锁住那个明确会崩的调用。
- ✅ 推荐:try { data = JSON.parse(input); } —— 仅包裹解析动作
- ❌ 反模式:try { console.log('parsing...'); data = JSON.parse(input); save(data); } —— 日志和保存不该进 try
- 更优解:用 input?.trim()?.startsWith('{') 或 safeParse() 封装,从源头降低异常发生率
大范围 try 会污染 JIT 和 CPU 缓存
过大的 try 块虽不增加运行时判断开销,但会干扰底层优化:
- JIT 编译器难以对该方法内联或向量化,因异常表增加了控制流复杂度
- CPU 分支预测器对受保护区域误判率上升,导致流水线清空、I-Cache 重载
- 热路径(如价格计算、缓存 key 生成)被异常处理逻辑“稀释”,L1 指令缓存命中率下降











