异常处理本身不是性能瓶颈,但滥用或设计不当会显著拖慢程序;高频路径应规避异常流程控制,优先用防御性api和前置校验,只捕获具体异常类型,避免宽泛捕获、循环内try-catch及finally中耗时操作。

异常处理本身不是性能瓶颈,但滥用或设计不当会显著拖慢程序——尤其在高频路径上。面试官真正想考察的,不是你能不能写 try-catch,而是你能否判断“这里该不该抛异常”“有没有更轻量的替代方案”“捕获方式是否精准且可控”。
高频路径必须规避异常流程控制
把异常当 if 用,是性能杀手。比如用 Integer.parseInt() 解析字符串再靠 NumberFormatException 回退,默认值,看似简洁,实则每次失败都触发完整堆栈创建、遍历和销毁。JVM/CLR 都不优化这种路径。
- 推荐改用
Integer.TryParse()(C#)、str.isdigit()+int()(Python)或正则预判(Java)等无异常分支逻辑 - 循环体内绝对避免 try-catch,哪怕只有一行可能出错;应把校验/转换逻辑提到循环外,或批量预处理
- 数组访问、集合取值等场景,优先用
.getOrDefault()、.containsKey()等防御性 API,而非靠IndexOutOfBoundsException或NullPointerException控制流
捕获粒度决定开销与可维护性
宽泛捕获(如 catch (Exception e) 或 except:)不仅掩盖问题,还会让 JVM/CLR 无法做内联、循环展开等关键优化,间接拉低整体执行效率。
- 只捕获明确知道如何处理的具体异常类型,比如
FileNotFoundException而非IOException全集 - 避免在 finally 中执行耗时操作(如远程日志上报),它会在每次执行路径中无条件运行
- 生产环境禁用
e.printStackTrace(),日志记录需控制字段粒度(如跳过完整堆栈,仅保留关键上下文)
异常应是兜底,不是主干逻辑
健壮 ≠ 处处 try-catch。真正高可用系统的设计原则是:90% 的异常靠前置校验拦截,剩下 10% 才靠异常机制兜底。这既降低开销,又提升可观测性。
- 参数非法、空指针、文件不存在等常见问题,应在入口处快速 fail-fast,而不是放行后靠异常暴露
- 自定义异常要有业务语义(如
InsufficientBalanceException),不为包装而包装;无业务含义的包装反而增加对象分配和堆栈开销 - 对可恢复错误(如网络超时),优先用重试+降级策略,而非依赖异常传播来驱动重试逻辑
评估异常开销不能只看单次
一次 throw 的耗时可能只有微秒级,但它的真实代价藏在三处:调用链深度(堆栈展开成本递增)、GC 压力(异常对象带完整堆栈快照)、以及 JIT 编译器对含 try 块方法的保守优化策略。
- 用 JFR(Java)、dotnet-trace(.NET)或
py-spy(Python)实际采样,重点关注异常抛出频次和调用栈深度 - 对比开启/关闭某段异常逻辑后的吞吐量与 P99 延迟变化,比单纯看 CPU 占用更有说服力
- 上线前检查 APM 工具中的异常率指标——持续高于 0.1% 就值得深挖,哪怕单次开销不大











