异常创建慢的核心是fillInStackTrace()的栈遍历,绕过它可提速20–60倍:重写该方法返回this,或用super(msg,null,false,false)禁用栈写入,仅适用于无需诊断的高频业务异常。

异常创建慢,核心不是内存分配,而是 fillInStackTrace() 主动采集调用栈——这个 native 操作会遍历每一帧、解析类名方法名、拼接字符串、缓存数组,耗时随栈深度近似平方增长。要让异常创建“飙升”变快,关键就是绕过它。
重写 fillInStackTrace() 直接返回 this
这是最直接、最可靠的方式。JVM 默认在构造末尾调用该方法填充堆栈,你把它变成空操作,就彻底跳过了整个采集链:
- 不调用
super.fillInStackTrace(),避免 JVM 执行栈帧遍历 - 返回
this(必须是Throwable子类型),保持链式调用兼容性 - 适用于
RuntimeException或Error子类;检查型异常(Exception)虽可继承,但编译器不强制要求栈信息,实际也可用
示例:
public class FastRejectException extends RuntimeException {public FastRejectException(String message) {
super(message);
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
用 writableStackTrace = false 构造参数禁用
JDK 7+ 提供了更底层的控制入口:在构造器中显式传入 false 关闭栈轨迹写入能力。这种方式无需重写方法,语义更清晰:
- 调用父类四参数构造器:
super(message, cause, enableSuppression, false) -
writableStackTrace = false会让fillInStackTrace()内部直接跳过采集逻辑 - 比重写方法更轻量,且不影响
getStackTrace()行为(返回空数组)
推荐写法:
public class ValidationException extends RuntimeException {public ValidationException(String message) {
super(message, null, false, false);
}
}
只对明确不需要诊断的场景启用
优化不是万能的,用错地方反而埋雷:
- 适合高频、可预期、纯业务逻辑拒绝:如参数校验失败、缓存未命中熔断、限流拒绝
- 不适合需排查根因的异常:如
IOException、SQLException、NullPointerException—— 它们一旦发生,堆栈就是唯一线索 - 注意日志框架行为:若用
log.error("", e),堆栈为空会导致日志缺失关键上下文,建议手动补上 method、key、timestamp 等字段 - 若异常被包装(如
new RuntimeException(e)),外层仍会触发自身fillInStackTrace(),需确保整条链都做同样处理或避免包装
实测效果与验证方式
这不是理论优化,而是可观测的性能跃升:
- 创建耗时从 1–3 μs 降至 50–100 ns,降幅达 20–60 倍
- CPU 火焰图中
Throwable.占比明显下降 - Young GC 频率降低,尤其在网关、风控等吞吐敏感服务中效果显著
- 验证是否生效:打印
e.getStackTrace().length,应为 0;若仍非零,检查是否被 re-throw、被监控 SDK 增强、或 IDE 断点强制填充









