本文目录导读:

这是一份关于 JMH(Java Microbenchmark Harness) 的完整实战案例解析,我将带你从环境搭建、编写基准测试到结果解读,逐步完成一个典型的性能测试案例。
案例目标
比较 StringBuilder、StringBuffer 和 String.concat() 在字符串拼接时的性能差异,并测试不同初始容量对 StringBuilder 的影响。
环境准备 (Maven依赖)
<dependencies>
<!-- JMH 核心库 -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.37</version>
</dependency>
<!-- JMH 注解处理 -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.37</version>
<scope>provided</scope>
</dependency>
</dependencies>
基准测试代码
package com.example.jmh;
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;
@BenchmarkMode(Mode.Throughput) // 吞吐量模式(每秒执行次数)
@OutputTimeUnit(TimeUnit.MILLISECONDS) // 输出时间单位
@State(Scope.Thread) // 每个线程独立实例
@Warmup(iterations = 5, time = 1) // 预热 5 轮,每轮 1 秒
@Measurement(iterations = 5, time = 1) // 正式测量 5 轮,每轮 1 秒
@Fork(2) // 2个JVM进程
public class StringConcatBenchmark {
// ---- 输入数据 ----
@Param({"10", "100", "1000"}) // 测试不同长度的字符串
private int length;
private String[] fragments;
@Setup
public void setup() {
// 预生成随机的字符串片段
fragments = new String[length];
for (int i = 0; i < length; i++) {
fragments[i] = "str-" + i + "-";
}
}
// ---- 测试1: String.concat() ----
@Benchmark
public String testStringConcat() {
String result = "";
for (String s : fragments) {
result = result.concat(s);
}
return result;
}
// ---- 测试2: StringBuilder(默认容量16) ----
@Benchmark
public String testStringBuilder() {
StringBuilder sb = new StringBuilder();
for (String s : fragments) {
sb.append(s);
}
return sb.toString();
}
// ---- 测试3: StringBuilder(预分配容量) ----
@Benchmark
public String testStringBuilderWithCapacity() {
// 预估算容量: 每段字符串平均长度 * 段数
int estimated = length * 8;
StringBuilder sb = new StringBuilder(estimated);
for (String s : fragments) {
sb.append(s);
}
return sb.toString();
}
// ---- 测试4: StringBuffer ----
@Benchmark
public String testStringBuffer() {
StringBuffer sb = new StringBuffer();
for (String s : fragments) {
sb.append(s);
}
return sb.toString();
}
// ---- 主函数: 启动JMH基准测试 ----
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(StringConcatBenchmark.class.getSimpleName())
.forks(2)
.build();
new Runner(opt).run();
}
}
运行方式
方式A: 主方法运行 (推荐)
直接运行 main 方法,JMH会自动进行所有测试。
方式B: 命令行运行 (Maven)
mvn clean package java -jar target/benchmarks.jar
输出结果解读
运行后,控制台会输出 每个Benchmark方法的详细性能报告,以下是典型的输出(节选):
Benchmark (length) Mode Cnt Score Error Units
StringConcatBenchmark.testStringBuffer 10 thrpt 10 215.367 ± 5.892 ops/ms
StringConcatBenchmark.testStringBuffer 100 thrpt 10 78.923 ± 2.105 ops/ms
StringConcatBenchmark.testStringBuffer 1000 thrpt 10 18.452 ± 0.887 ops/ms
StringConcatBenchmark.testStringBuilder 10 thrpt 10 280.441 ± 9.124 ops/ms
StringConcatBenchmark.testStringBuilder 100 thrpt 10 110.235 ± 3.456 ops/ms
StringConcatBenchmark.testStringBuilder 1000 thrpt 10 23.889 ± 1.012 ops/ms
StringConcatBenchmark.testStringBuilderWithCapacity 10 thrpt 10 295.112 ± 11.008 ops/ms
StringConcatBenchmark.testStringBuilderWithCapacity 100 thrpt 10 125.678 ± 4.229 ops/ms
StringConcatBenchmark.testStringBuilderWithCapacity 1000 thrpt 10 28.334 ± 1.567 ops/ms
StringConcatBenchmark.testStringConcat 10 thrpt 10 45.238 ± 2.334 ops/ms
StringConcatBenchmark.testStringConcat 100 thrpt 10 12.678 ± 0.567 ops/ms
StringConcatBenchmark.testStringConcat 1000 thrpt 10 3.452 ± 0.123 ops/ms
结果分析:
| 方法 | 10段 | 100段 | 1000段 | |
|---|---|---|---|---|
| String.concat | 45 ops/ms | 12 ops/ms | 4 ops/ms | 最慢,性能随长度急剧下降 |
| StringBuffer | 215 ops/ms | 78 ops/ms | 18 ops/ms | 次之,有同步开销 |
| StringBuilder | 280 ops/ms | 110 ops/ms | 24 ops/ms | 快,无同步开销 |
| StringBuilder+Capacity | 295 ops/ms | 125 ops/ms | 28 ops/ms | 最快,预分配容量减少了扩容 |
进阶技巧:Blackhole 与死代码消除
在上面的例子中,如果不返回结果,但为了防止JIT认为结果未被使用而优化掉,可以使用 Blackhole。
@Benchmark
public void testAppendWithBlackhole(Blackhole bh) {
StringBuilder sb = new StringBuilder();
for (String s : fragments) {
sb.append(s);
}
// 将结果传递给 Blackhole,避免死代码消除
bh.consume(sb.toString());
}
进阶技巧:多线程测试
@Benchmark
@Threads(4) // 4个线程并发执行
public String testStringBuilderThreaded() {
StringBuilder sb = new StringBuilder();
for (String s : fragments) {
sb.append(s);
}
return sb.toString();
}
常见陷阱与规避
陷阱1: 循环展开 (Loop Hoisting)
@Benchmark
public void testWrong() {
int x = 1;
for (int i = 0; i < 1000; i++) {
x = x + i; // JIT 可能会将整个循环优化掉
}
}
// 修正: 通过 Blackhole 消费结果
@Benchmark
public void testCorrect(Blackhole bh) {
int x = 1;
for (int i = 0; i < 1000; i++) {
x = x + i;
}
bh.consume(x);
}
陷阱2: 常数折叠 (Constant Folding)
// 不推荐: 直接写死参数
@Benchmark
public int testConst() {
return Math.sqrt(2); // JIT 会提前计算出结果
}
// 推荐: 使用 @Param 注入
@Benchmark
public int testParam(@Param("2") double x) {
return (int) Math.sqrt(x);
}
陷阱3: 误用状态 (State)
- @State(Scope.Thread): 每个线程单独实例,适合无竞争的场景。
- @State(Scope.Benchmark): 所有线程共享同一个实例,测试并发竞争。
- @State(Scope.Group): 用于分组测试(
@Group注解)。
完整配置文件示例 (JSON格式)
还可以通过 -prof 参数启用分析器:
java -jar target/benchmarks.jar -prof stack -prof gc -prof perf
这样会额外输出 堆栈采样、GC次数/时间、CPU硬件计数器,帮助你做更深入的分析。
JMH最佳实践清单
- 每个
@Benchmark方法只测一件事,不要混入无关逻辑。 - 使用
@Setup初始化数据,避免在循环中执行IO、网络等操作。 - 总是使用
Blackhole消耗结果。 - 适当预热 (至少3-5轮) 让JIT充分编译。
- 使用多个
@Fork避免测试间相互影响。 - 用
@Param控制变量,而不要硬编码。
现在你可以复制上述代码,直接运行 main 方法查看结果,如果遇到任何报错或需要更详细的解释,欢迎随时追问!
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播