## 为什么需要 JMH

  • JVM 会进行 JIT 优化、内联与逃逸分析;直观写的微基准容易偏差。
  • JMH 提供标准的 Runner、注解与统计方法,能较好避开测量伪影。

## 最小可运行示例

import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(2)
@State(Scope.Thread)
public class HelloJMH {

    @Benchmark
    public int add() {
        int a = 1, b = 2;
        return a + b;
    }
}

运行:

mvn -q -DskipTests package
java -jar target/benchmarks.jar

## Blackhole 与常见陷阱

  • 不使用结果会被优化掉;用 `Blackhole` 消耗结果避免死代码消除。
  • import org.openjdk.jmh.infra.Blackhole;
    
    @Benchmark
    public void compute(Blackhole bh) {
        int s = 0;
        for (int i = 0; i < 1000; i++) s += i;
        bh.consume(s);
    }
    
  • 避免在基准方法中执行 IO/网络;将外部依赖隔离到 `@Setup` 或模拟。

## 参数化与状态

@State(Scope.Thread)
public class ParamState {
    @Param({"16","64","256"})
    int size;
    int[] data;

    @Setup(Level.Trial)
    public void setup() {
        data = new int[size];
        for (int i = 0; i < size; i++) data[i] = i;
    }
}

@Benchmark
public int sum(ParamState st) {
    int s = 0;
    for (int v : st.data) s += v;
    return s;
}

## 预热、测量与 Fork

  • 预热用于 JIT 暖机;测量阶段才统计结果。
  • Fork 会在独立 JVM 中运行,降低 JVM 状态干扰;建议 `Fork >= 2`。
  • 保持稳定环境:固定 CPU 频率与线程数;避免同时运行其他负载。

## 逃逸分析与对象分配

  • 小对象可能被标量替换或栈上分配;在微基准中会影响结论。
  • record Pair(int a, int b) {}
    
    @Benchmark
    public int scalarReplace() {
        Pair p = new Pair(1, 2);
        return p.a() + p.b();
    }
    
  • 用 JFR 或 GC 日志观察分配速率与暂停;确认结果与假设一致。

## 结果解读

  • 关注误差范围(`±`)与单位,优先比较量级与趋势。
  • 对关键参数做单因素变化(例如 `size` 或算法实现),避免混杂变量。

## 验证建议

  • 与端到端压测结合:微基准仅反映局部,实现更改需在整体负载验证。
  • 固定 JVM 参数(如堆大小与 GC),保证不同运行的可比性。

## 注意事项

  • 关键词与主题一致:JMH、微基准、逃逸分析、预热与 Fork。
  • 分类匹配:`软件/编程语言/Java`。
  • 描述准确:强调可信测量与可复现方法。
  • 技术参数已验证:JMH 注解与运行方式为常规用法;对象分配与逃逸分析为 JVM 通用行为。


点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部