跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

Java中的异常性能优化_利用栈迹擦除(StackTrace Elimination)提升速度

是栈迹采集开销大而非长度导致慢;需重写fillInStackTrace()返回this来跳过JVM栈遍历,仅适用于无需诊断的校验异常,注意APM兼容性与框架约束。 Java异常抛出慢,真是因为栈迹太长? 不是栈迹“长”导致慢,而是每次
Throwable
实例化时默认强制采集完整栈迹——这个动作本身开销大,且后续若没被打印或日志捕获,栈迹就纯属浪费。JVM 并不自动“擦除”,所谓“栈迹擦除”是开发者主动放弃采集的优化手段。 仅在明确不需要诊断信息的场景下启用,比如业务校验失败快速返回(如参数非法、状态不满足) 不要对
IOException
、
SQLException
等需排查外部依赖问题的异常使用 Java 8 及以前版本无内置支持;Java 9+ 可通过
Throwable#fillInStackTrace()
覆盖实现,但更常用的是自定义异常类 + 空实现 怎么写一个不采集栈迹的运行时异常? 核心是重写
fillInStackTrace()
方法并直接返回
this
,跳过 JVM 的栈遍历逻辑。注意:必须继承
RuntimeException
或
Error
,检查型异常(
Exception
子类)无法绕过编译器对栈迹的隐式要求。
public class FastValidationException extends RuntimeException { public FastValidationException(String message) { super(message); } @Override public Throwable fillInStackTrace() { return this; // 关键:不调用 super.fillInStackTrace() } }
构造函数里仍会走
super(message)
,但只要不触发
fillInStackTrace()
,就不会采集栈 如果还调用了
initCause()
或设置了
suppressed
异常,需一并重写对应方法,否则可能意外触发栈采集 避免在
toString()
或
printStackTrace()
中间接调用
fillInStackTrace()
—— 这些方法本身不触发,但若父类逻辑有副作用则需验证 用了栈迹擦除,为什么日志里还是看到行号? 日志框架(如 Logback、Log4j2)默认调用
Throwable#getStackTrace()
获取数组,而该数组内容取决于
fillInStackTrace()
是否执行过。如果你看到行号,说明栈迹已被填充——常见原因有: 异常被 catch 后又 re-throw,且未用新实例(
throw e;
保留原栈,
throw new FastValidationException(...);
才生效) IDE 调试时启用了 “exception breakpoint”,JVM 在抛出瞬间强制填充栈迹用于断点定位 某些监控 SDK(如 SkyWalking、Pinpoint)会在字节码增强阶段插入栈采集逻辑,与你的重写冲突 使用了
Thread.currentThread().getStackTrace()
手动采集,这和异常实例无关,不受
fillInStackTrace()
控制 性能提升到底有多少?要不要上? 单次抛出可快 3–10 倍(取决于栈深度),但真实收益取决于异常发生频率和是否真被 throw 出去。高频校验场景(如 API 网关参数检查、序列化预校验)值得引入;低频或必查根因的异常(如数据库连接失败)加了反而掩盖问题。 Eclipse导入Android或其他的JAVA项目的正确方法 WORD版 本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看 下载 立即学习 “ Java免费学习笔记(深入) ”; 压测时别只看吞吐量,重点观察 GC 压力下降——因为短生命周期的
StackTraceElement[]
对象大幅减少 禁止全局替换所有异常为“无栈版”,尤其不能动 Spring 的
RuntimeException
子类(如
IllegalArgumentException
),它们可能被框架内部依赖栈迹做分类处理 上线前务必确认 APM 工具兼容性:部分工具靠栈迹做错误聚类,擦除后会导致告警丢失或误合并 真正难的不是写那个
return this;
,是怎么在团队里说清:这里不填栈迹是权衡,不是偷懒;而线上突然少了一堆“有用”的堆栈,得有人兜底查问题。

相关文章