应放心使用 try-catch,但严禁用异常控制流程;其本身无性能开销,真正代价在于 throw 操作的堆分配、栈遍历与展开,需通过前置检查、具体捕获、单条隔离等方式规避可预期异常。

不需要刻意避免 try-catch 本身,但必须严格避免在高性能场景中频繁抛出异常。
try-catch 包裹本身几乎没有开销
现代 JVM(如 HotSpot)对正常执行路径做了深度优化:只要不抛异常,try-catch 块只是字节码里的一段异常表记录,运行时完全不参与控制流判断。JIT 编译器还会把热路径中“从不触发”的异常处理逻辑彻底优化掉。实测表明,在循环内或方法入口加一层空 try-catch,和不加相比,吞吐量差异通常在 ±2% 以内,属于噪声范围。
真正拖慢性能的是 throw new Exception()
每次抛异常都会触发三件高成本操作:
- 分配异常对象(含堆内存申请与 GC 压力)
- 调用 fillInStackTrace() —— 遍历当前线程完整调用栈,提取每帧的类名、方法名、行号,生成数组
- 栈展开(stack unwinding)—— 逐层退出方法,查找匹配的 catch 块
举个例子:一个 20 层深的调用链抛一次 RuntimeException,耗时通常是普通对象创建的 10–50 倍。如果每毫秒都抛一次,CPU 很快就会卡在栈遍历上。
高性能场景下的关键避坑点
不是“少用 try-catch”,而是“别把异常当流程分支”:
-
禁止用异常做条件判断:比如用
Integer.parseInt()+ catch NumberFormatException 来校验字符串是否为数字——应改用StringUtils.isNumeric()或正则预判 - IO 或网络调用要前置检查:文件存在再读,连接可用再发请求,避免因可预期失败而触发异常
- 批量处理时隔离单条失败:比如解析 1000 条日志,宁可在循环内 try-catch 单条(不抛异常时无负担),也不要让一条错误导致整批中断
-
捕获具体异常类型:用
catch (IOException e)替代catch (Exception e),减少类型匹配开销,也提升语义清晰度
该用还是不该用?看两个信号
放心用 try-catch 的情况:
- 包裹的是真实可能出错的外部依赖(数据库查询、HTTP 调用、文件读写)
- catch 块里只做轻量恢复或日志记录,不嵌套复杂逻辑
需要重构的情况:
- 日志里高频出现同一异常堆栈(说明错误可预测,应转为防御性检查)
- JFR 或 profiler 显示
Throwable.fillInStackTrace占 CPU 时间 Top 3











