new exception() 开销大是因为 fillinstacktrace() 遍历栈帧生成字符串,耗时是普通对象的10–100倍;异常不应作控制流,避免在高频路径中创建;优先复用异常或精简上下文。

为什么 new Exception() 本身就有开销
异常对象创建慢,不是因为堆分配本身,而是因为 Throwable 构造时默认会触发栈追踪(stack trace)——即调用 fillInStackTrace()。这个操作要遍历当前线程所有栈帧,生成字符串、解析类名、行号等,耗时是普通对象的 10–100 倍(视栈深度而定)。
- 哪怕你没抛出,只写
new RuntimeException(),开销已经发生 - JIT 后期可能做部分优化(如栈追踪惰性化),但不可依赖,尤其在低版本 JVM 或高并发场景下
-
Exception和RuntimeException子类行为一致,开销无本质区别
哪些场景容易误踩“异常当控制流”的坑
典型错误是把异常用作逻辑分支:比如用 NumberFormatException 判断字符串是否为数字,或靠 NullPointerException 检测空值。这类写法在吞吐量稍大的服务里会立刻暴露性能问题。
- 日志系统中频繁用
new Exception().getStackTrace()打印上下文 → 改用Thread.currentThread().getStackTrace()(不触发 fillInStackTrace) - DAO 层用
EmptyResultDataAccessException表示“查不到” → 应返回Optional或null,让上层决定是否报错 - JSON 解析失败后反复重试并捕获
JsonProcessingException→ 先校验格式再解析,或缓存解析器实例减少重复异常构造
try-catch 块本身几乎没开销,但别信“不抛就不花时间”
现代 JVM(JDK 8u292+)对空 try 块做了很好优化,字节码层面几乎零成本。但一旦进入 catch 分支,尤其是首次触发时,JVM 需要定位异常处理器、展开栈、填充异常对象——这些动作无法规避。
- “没抛异常所以 try-catch 很快”是对的;但“我 catch 了就能随便 throw”是错的
- 嵌套过深的
try(如循环内)会增大方法内联难度,间接影响 JIT 编译效果 - 某些监控/诊断工具(如 Arthas)开启异常增强时,会让所有
throw都变慢,此时更需避免非必要异常
真要记录异常上下文?优先复用 + 精简
如果业务确实需要运行时异常信息(比如审计、调试),与其每次 new 一个新 Exception,不如提前构造好、按需填充字段,或直接拼接轻量字符串。
立即学习“Java免费学习笔记(深入)”;
- 用静态常量异常替代动态创建:
private static final RuntimeException NOT_FOUND = new RuntimeException("not found"); - 继承
RuntimeException并重写fillInStackTrace()返回this(慎用,会丢失原始栈,仅限明确不需要栈信息的场景) - 日志中记录异常时,用
log.debug("failed to parse {}", input, ex)而非log.debug("failed to parse " + input + ", ex=" + ex)—— 后者会提前触发ex.toString(),间接调用栈遍历
fillInStackTrace 上。










