JMH是专用于Java微基准测试的官方工具,能规避JIT编译、死码消除、GC干扰等问题,通过预热、进程隔离和Blackhole防优化等机制,提供纳秒/微秒级可信性能数据。

Java 异常处理的性能损耗不能靠 System.nanoTime() 简单循环测,因为 JIT 编译器会内联、逃逸分析甚至完全消除无用的 throw,导致结果严重失真。真实有效的基准测试必须用 JMH(Java Microbenchmark Harness),它能控制热身、隔离进程、防止死码消除,并提供纳秒/微秒级可信数据。
为什么普通写法测不准
手动 for 循环 + 时间差测量有三大硬伤:
- JIT 可能把整个异常抛出逻辑优化掉,尤其当异常没被使用或 catch 后未做任何事
- GC 干扰大:频繁 new 异常对象会触发新生代回收,把 GC 耗时混进测量结果
- 栈深度、方法内联状态等运行时上下文不固定,每次执行环境差异大
JMH 正确配置要点
核心是让测试反映“真实 throw 开销”,而非被 JVM 偷懒绕过:
- 用
@Fork(1)启新 JVM 进程,避免不同 benchmark 互相污染 - 加
@Warmup(iterations = 3)和@Measurement(iterations = 10),确保 JIT 已充分优化 - 输出单位设为微秒:
@OutputTimeUnit(TimeUnit.MICROSECONDS)(异常耗时在 1–5 μs 量级) - 关键一步:在
throw后调用Blackhole.consume(),强制 JVM 保留该语句,防止优化掉
对比组设计要闭环
不能只测 “throw 异常”,必须和业务中真正替代方案横向比:
立即学习“Java免费学习笔记(深入)”;
-
throw new MyException("msg")vsreturn Result.error("msg") -
throw new IllegalArgumentException()vsif (invalid) return; - 带 fillInStackTrace() 的异常 vs 重写该方法返回
this的异常
每组都应复用相同输入、相同校验逻辑,仅改变错误传递方式,才能看出真实差距(实测可达 100–500 倍)。
自定义异常的针对性压测
若你用的是自定义异常,需额外验证两个关键点:
- 是否重写了
fillInStackTrace()?默认实现遍历整个调用栈,耗时随栈深度线性增长 - 是否设置了
writableStackTrace = false?可跳过栈采集,但仅适用于诊断信息完全不需要的场景(如协议解码失败)
建议在 JMH 中分别构造这三类异常:未重写、重写返回 this、构造时传 false 关闭栈记录,直接看微秒级差异。



















