jmh需用maven-shade-plugin打包为可执行jar并java -jar运行,禁用ide直接执行;@fork(0)导致预热失效,@fork(1)为合理起点;状态作用域选错引发数据污染或重复初始化;blackhole用于防止jit优化,但不可滥用。

怎么让JMH项目跑起来不报NoClassDefFoundError或ClassNotFoundException
JMH不是直接加个依赖就能用的库,它依赖一套独立的构建和运行机制。常见错误是只在pom.xml里加了jmh-core,然后直接写main方法调用Runner.run()——这会炸,因为JMH的注解处理器、字节码生成、fork进程启动全靠Maven插件驱动。
必须用maven-shade-plugin打包成可执行jar,并通过java -jar运行,而不是IDE里点绿色三角形。否则类路径缺失、注解没处理、基准方法没生成都会导致找不到类。
- 确保
pom.xml中启用jmh-generator-annprocess注解处理器(Java 8+需显式配置) -
maven-shade-plugin的transformer要包含ServicesResourceTransformer,否则SPI加载失败 - 运行命令必须是
java -jar target/benchmarks.jar,不能java -cp ... org.openjdk.jmh.Main
为什么@Fork设成0会出问题,又为什么不该盲目设成10
@Fork(0)看似省事,实则绕过JVM预热隔离,把基准测试和JVM启动、类加载、JIT编译全混在一起,结果波动极大,尤其对短耗时方法几乎不可信。而@Fork(10)不是“越多越好”:每个fork都是全新JVM进程,内存开销大,CI机器上容易OOM;且fork太多会让总耗时飙升,掩盖单次执行的噪声特征。
- 默认
@Fork(1)是合理起点;稳定性要求高时用@Fork(3),兼顾复现性和资源消耗 - 若需测GC影响,可配合
jvmArgsAppend = "-XX:+PrintGCDetails",但别在生产环境参数上盲目套用 - 注意
@Fork(jvmArgs = {...})里的参数会被所有fork继承,不要在里面写-Xmx2g这种固定值,除非你确定每轮都需相同堆大小
@State(Scope.Benchmark)和@State(Scope.Thread)选错会怎样
状态对象生命周期直接决定共享程度。用Scope.Benchmark却在方法里改字段,多个线程会踩脏数据;用Scope.Thread却试图在@Setup里初始化全局缓存,结果每个线程重复加载,测出来的是初始化耗时而非真实逻辑。
立即学习“Java免费学习笔记(深入)”;
- 需要跨线程共享(如连接池、预热好的Map)→
@State(Scope.Benchmark),且字段必须线程安全或只读 - 每个线程独享(如临时缓冲区、随机数生成器)→
@State(Scope.Thread),避免同步开销 - 千万别在
Scope.Benchmark对象里放Random或StringBuilder这类非线程安全对象,JMH不会帮你同步
为什么Blackhole.consumeCPU()不能乱用,又为什么有时非用不可
JVM可能把没被使用的计算结果整个优化掉,比如int x = compute();后面没用x,JIT就直接删掉整行。这时候Blackhole.consumeCPU(n)能“骗过”JIT,但它本身有开销,且只适用于CPU密集型场景。对IO或锁竞争类测试,它反而引入干扰。
- 纯计算逻辑(如数学运算、字符串解析)→ 用
blackhole.consume(result)或blackhole.keepData(result) - 返回对象引用时优先用
blackhole.consume(result),避免触发不必要的GC - 如果方法本身含
System.out.println或File.write,不用Blackhole也行——副作用已阻止优化











