智睿享
白蓝主题五 · 清爽阅读
首页  > 软件指南

异常捕获真有性能代价?聊聊那些被忽略的细节

写代码时,很多人习惯性地加上 try-catch,觉得“反正捕不捕,安全第一”。但你有没有发现,某些系统在高并发下突然变慢,查来查去最后发现是异常被频繁抛出,而 catch 块成了隐形拖油瓶?

异常不是免费的

很多人误以为 try-catch 包裹代码就像穿了件外套,不触发就毫无影响。其实不然。try 本身开销很小,现代 JVM 和 .NET 运行时对正常执行路径做了优化,没抛异常时几乎没负担。但一旦抛出异常,代价立马显现。

抛异常的本质是生成一个堆栈跟踪(stack trace),这个过程需要遍历调用栈,逐层记录方法名、行号、类信息。这个操作耗时远超普通逻辑判断,尤其在深层嵌套或递归调用中更明显。

别拿异常当流程控制

下面这种写法你可能见过:

try {
    int result = Integer.parseInt(input);
} catch (NumberFormatException e) {
    System.out.println("输入不是数字");
}

看起来没问题,但如果 input 经常不是数字,那就等于把异常当成常规判断在用。更好的方式是先校验:

if (input.matches("\\d+")) {
    int result = Integer.parseInt(input);
} else {
    System.out.println("输入不是数字");
}

这样避免了不必要的异常抛出,性能更稳。

生产环境里的真实案例

有次排查一个订单处理服务,发现每到促销时段响应时间飙升。日志里看到大量 IOException,但磁盘和网络都正常。深入一看,原来是某个工具类在读取配置时,每次都尝试从远程拉取,失败就抛异常,再降级本地。问题在于,它默认远程不可用,等于每次启动都靠“异常驱动”走降级逻辑。

改法很简单:先探测远程是否可达,不可达时直接走本地,不再依赖异常流程。上线后,启动时间从 8 秒降到 1.2 秒,GC 次数也明显减少。

异常该用在哪

异常适合处理真正“异常”的情况,比如文件突然被删除、数据库连接中断、网络超时。这些是无法预见且不应频繁发生的场景。在这种前提下,捕获异常是合理且必要的。

但如果你的业务逻辑里,某种“错误”出现频率很高,比如用户输错验证码、参数格式不对,那就该用返回码、状态枚举或 Optional 这类机制,而不是靠异常来传递信息。

小建议:别滥用 finally 和 AutoCloseable

虽然 try-with-resources 很方便,但资源关闭本身也有开销。比如频繁打开关闭 FileInputStream,即使没异常,也会涉及系统调用。这时候考虑缓存连接或使用池化技术更合适。

异常捕获本身不会让程序变慢,慢的是频繁抛出和构建异常。只要别把异常当 if-else 用,大多数场景下完全不用担心性能问题。