1.微基准测试的必要性
开发者常常想比较两种实现的性能(如ArrayList与LinkedList、序列化框架JSON与Protobuf)。但是,简单的for循环测试结果不可靠,因为JVM会执行JIT编译、死代码消除、优化掉循环等。JMH(JavaMicrobenchmarkHarness)是OpenJDK官方的微基准测试框架,能够准确地测量Java代码性能。
参考:https://www.xrzqr.cn/category/weather-science.html
2.JMH的核心概念
状态(State):基准测试的状态对象,可定义@State(Scope.Benchmark)(所有线程共享)或@State(Scope.Thread)(每个线程单独)。
基准测试方法:使用@Benchmark标注。
设置/清理:@Setup、@TearDown标注初始化方法。
执行模式:
Throughput(单位时间执行次数)
AverageTime(平均执行时间)
SampleTime(执行时间分布)
SingleShotTime(单次执行,忽略JIT预热)
预热:@Warmup指定迭代次数,让JIT充分优化后再测量。
分叉:@Fork指定独立JVM进程数,避免测试间干扰。
3.编写一个简单基准测试
假设要比较StringBuilder和StringBuffer的append性能。使用JMH:
添加依赖org.openjdk.jmh:jmh-core和jmh-generator-annprocess。
定义@State(Scope.Thread)类,包含StringBuilder和StringBuffer实例。
编写两个@Benchmark方法,分别调用append。
运行JMH的main方法,输出结果。
JMH会输出每个操作的纳秒级别时间,并附带置信区间。
参考:https://www.xrzqr.cn/category/national-weather.html
4.常见陷阱与避免
死代码消除:如果基准方法的结果没有被使用,JVM可能会优化掉整个计算。使用Blackhole.consumeCPU或return结果。
常量折叠:如果输入是常量,JVM可能在编译期计算完。应使用@State参数化输入,或从java.util.concurrent.ThreadLocalRandom获取。
虚假共享:多线程访问相邻缓存行。JMH提供@CompilerControl和@AuxCounters帮助检测。
5.案例:比较JSON序列化库
某项目需要选择JSON库(JacksonvsGsonvsFastjson)。使用JMH编写基准测试:
准备相同复杂度的Java对象。
分别序列化和反序列化,测量吞吐量。
设置不同数据大小(小对象、大数组)。
运行结果表明,Jackson在吞吐量上领先Gson约30%,但Gson启动更快。最终团队根据JMH报告选择了Jackson。
6.集成到CI
JMH可以作为独立的JAR运行,并输出JSON格式的结果,方便CI系统收集并绘制性能趋势图。例如,每次Git提交后自动运行关键基准测试,对比历史数据,如果性能下降超过阈值,则报警。
7.与其他工具对比
System.nanoTime简单循环:不可靠。
JMH:官方标准,处理了JVM预热、死代码消除、分叉等,结果可信。
8.总结
JMH是Java开发者进行性能比较的利器。无论是评估数据结构、库、算法,还是验证优化效果,JMH都能提供可信的数据。学会JMH可以让你摆脱“感觉快”的感性判断,用数据驱动决策。
参考:https://www.xrzqr.cn