禁用栈追踪是最直接的优化方式,通过覆写fillInStackTrace()返回this;其次可缓存固定栈轨迹复用;慎用Error替代Exception;根本解法是避免异常驱动控制流,改用布尔值、Optional等结构。

频繁创建 Exception 实例开销大,核心瓶颈不在对象分配本身,而在于 fillInStackTrace() 的默认实现——它会遍历当前线程的整个调用栈、解析每一帧(包括类名、方法名、行号等),并生成完整的栈轨迹。这个过程涉及大量反射和字符串操作,耗时显著,尤其在高频异常(如业务校验失败)场景下尤为明显。
禁用栈追踪:重写 fillInStackTrace 返回 this
如果异常仅用于控制流(如中断循环、标记失败状态),且不需要真实堆栈信息,最直接的优化是让 fillInStackTrace() 不做任何事:
- 继承
RuntimeException或Exception - 覆写
fillInStackTrace()方法,直接返回this
例如:
public class FastValidationException extends RuntimeException {
@Override
public Throwable fillInStackTrace() {
return this; // 跳过栈收集
}
}
缓存固定栈轨迹:复用预构建的 StackTraceElement[]
若仍需“有栈信息”(如日志可读性),但不强求实时调用点,可在类初始化时预生成一份固定栈轨迹,每次抛出都复用它:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 在静态块中调用一次
new Exception().getStackTrace(),保存数组 - 覆写
fillInStackTrace()为无操作,再覆写getStackTrace()返回缓存数组
注意:这样所有实例共享同一份栈(指向该类的 static 块),适合“已知错误位置”的场景,比如统一校验入口。
用 Error 替代 Exception(谨慎使用)
Error 及其子类(如 AssertionError)默认不收集栈(JVM 层面优化),且语义上表示严重问题。若业务逻辑允许将某些高频失败视为“不可恢复错误”,可考虑自定义 Error 子类并禁用栈填充。但需注意:Error 通常不被捕获,可能影响代码可维护性,慎用。
更根本的解法:避免异常驱动控制流
性能最优的方式其实是不抛异常:
- 用布尔返回值、Optional、Result 封装类表达成功/失败
- 校验逻辑前置,失败时直接返回错误码或消息,不走异常路径
- 仅在真正“异常”(罕见、不可预期)时才用 Exception
Java 的异常机制设计初衷就是“异常情况”,高频抛异常违背语义,也绕不开 JVM 的栈采集开销。优化 fillInStackTrace() 是权宜之计,重构控制流才是长期方案。

















