告别“我觉得”!用 JMH 搞懂你的 Java 代码性能

项目开发中,经常会遇到这样的情况:辛辛苦苦写了一段代码,觉得自己优化得不错,性能肯定提升了。 然而, “我觉得”往往不靠谱。 没有经过严谨的测试,一切都是空谈。 这时候,你就需要 JMH (Java Microbenchmark Harness) 这个利器了。

JMH 是什么?它能做什么?

简单来说,JMH 是 Oracle 官方提供的、专门用于 Java 代码性能测试的框架。 它可以帮你

  • 精确测量代码片段的性能: 避免手写测试代码带来的误差,例如 JVM 预热、JIT 优化等。
  • 发现潜在的性能瓶颈: 通过各种统计指标,例如平均执行时间、吞吐量等,让你对代码的性能一目了然。
  • 对比不同实现的性能差异: 轻松对比多种方案,选择最优解。
  • 避免常见的性能测试陷阱: JMH 会自动处理很多细节,让你专注于测试逻辑本身。

为什么要用 JMH?“土法炼钢”不行吗?

可能有些同学会说:“我自己写个 main 方法,跑个几百万次,也能测出性能啊!” 听起来似乎可行,但其实有很多坑

  • JVM 预热 (Warmup): JVM 在程序刚启动时,性能往往比较差,需要经过一段时间的预热才能达到最佳状态。 手动测试很难准确控制预热时间。
  • JIT 优化: JVM 会对热点代码进行即时编译 (JIT),将其编译成本地代码,从而提高性能。 手动测试很难模拟 JIT 优化后的真实情况。
  • 死代码消除 (Dead Code Elimination): 如果你的测试代码中有些部分没有被实际使用,JVM 可能会将其优化掉,导致测试结果不准确。
  • 伪共享 (False Sharing): 在多线程环境下,不同线程操作相邻的内存区域时,可能会导致缓存失效,从而影响性能。

而 JMH 会自动处理这些问题,保证测试结果的准确性和可靠性。

JMH 快速上手:一个简单的例子

说了这么多,不如来个实际例子。 假设我们要测试两种字符串拼接方式的性能

    1. 使用 + 运算符
    1. 使用 StringBuilder

首先,我们需要添加 JMH 的依赖。 如果你使用 Maven,可以在 pom.xml 文件中添加以下内容

 

xml

代码解读

复制代码

<dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> <version>1.36</version> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>1.36</version> </dependency>

然后,创建一个测试代码 ,内容如下

 

java

代码解读

复制代码

import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; @State(Scope.Thread) public class StringConcatBenchmark { @Param({"10", "100", "1000"}) public int length; private String str; @Setup(Level.Trial) public void setup() { str = "a"; } @Benchmark @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) public void testStringConcat(Blackhole blackhole) { String result = ""; for (int i = 0; i < length; i++) { result += str; } blackhole.consume(result); } @Benchmark @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) public void testStringBuilder(Blackhole blackhole) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < length; i++) { sb.append(str); } blackhole.consume(sb.toString()); } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(StringConcatBenchmark.class.getSimpleName()) .forks(1) .warmupIterations(5) .measurementIterations(5) .build(); new Runner(opt).run(); } }

  • @State(Scope.Thread): 表示每个线程都拥有一个 StringConcatBenchmark 实例。
  • @Param({"10", "100", "1000"}): 表示 length 字段有三个不同的取值,JMH 会分别对这三个值进行测试。
  • @Setup(Level.Trial): 表示在每次测试之前都会执行 setup 方法,用于初始化 str 字段。
  • @Benchmark: 表示这是一个需要进行性能测试的方法。
  • @BenchmarkMode(Mode.AverageTime): 表示以平均执行时间作为测试指标。
  • @OutputTimeUnit(TimeUnit.NANOSECONDS): 表示测试结果以纳秒为单位输出。
  • Blackhole.consume(result): 用于防止 JIT 优化将 result 变量优化掉,保证测试的准确性。

最后,运行 main 方法,就可以看到 JMH 的测试结果了。

JMH 进阶用法

除了上面介绍的基本用法,JMH 还有很多高级功能,例如

  • 使用 @CompilerControl 控制 JIT 优化
  • 使用 @BenchmarkMode 选择不同的测试模式 (例如吞吐量模式、单次执行时间模式)
  • 自定义 State 对象,实现更复杂的测试场景
  • 使用 JMH 的 API 手动控制测试过程

总结

JMH 是一个非常强大的 Java 性能测试框架。 掌握 JMH,可以让你对自己的代码性能更有信心,避免“我觉得”式的盲目优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值