Java异常性能开销核心在于fillInStackTrace()生成堆栈跟踪,而非try-catch本身;new Exception()即触发该开销,与是否throw无关;优化应避免无谓创建,优先用if/Optional、复用静态异常实例或禁用栈追踪。

Java异常处理的性能开销,核心不在try-catch语法本身,而在于异常对象创建时自动生成堆栈跟踪(StackTrace)这一动作。只要执行new Exception()或new RuntimeException(),开销就已发生——哪怕没抛出、没捕获。
fillInStackTrace()是真正的性能瓶颈
所有Throwable子类在构造时,默认调用fillInStackTrace()(一个native方法),它会同步遍历当前线程全部栈帧,逐帧提取类名、方法名、行号等信息,并格式化为字符串数组存入stackTrace字段。这个过程不是简单内存分配,而是涉及:
- 反射调用获取符号信息(尤其Lambda、动态代理会加剧开销)
- 大量字符串拼接与对象创建
- 无法被JIT有效内联或跳过
- 耗时与栈深度近似平方增长:栈深5层约0.2ms,栈深20层可达3ms以上
“没throw就没事”是个常见误解
很多开发者以为只写new IllegalArgumentException()但不throw,就不会有代价。事实是:该语句执行完毕,fillInStackTrace()已完成,CPU时间已被消耗。日志中写log.error("fail", new IOException())同样触发完整栈采集,后续日志框架还可能再次调用printStackTrace(),造成重复开销。
高频场景下的典型误用
这些做法看似简洁,实则把异常当控制流,极易引发性能问题:
立即学习“Java免费学习笔记(深入)”;
- 用
Integer.parseInt(str)配合NumberFormatException判断字符串是否为整数 - DAO层查不到数据时抛
EmptyResultDataAccessException,而非返回Optional - 循环中每轮都
new ParseException()解析CSV失败项 - 参数校验靠
Objects.requireNonNull()抛NullPointerException,而非提前if (obj == null)
可落地的优化手段
关键不是不用异常,而是避免无谓创建:
- 预期性检查优先用
if、Optional、Guava的Ints.tryParse()等零异常API - 必须抛异常时,复用
static final实例(如private static final IllegalStateException ILLEGAL = new IllegalStateException();) - 自定义异常可重写
fillInStackTrace()直接返回this,跳过栈采集(适用于无需调试上下文的内部状态错误) - JDK 8+支持禁用栈追踪:
new RuntimeException("", null, false, false) - 日志记录避免
e.printStackTrace(),改用logger.error("msg", e),让日志框架按需格式化



















