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