Java JMH常见的基准测试场景代码实例解读

 更新时间:2025年08月30日 08:53:15   作者:学亮编程手记  
本文介绍Java JMH性能测试框架,涵盖方法比较、参数影响、吞吐量及多线程测试场景,通过@Benchmark、@State、@Param等注解定义基准,结合预热和优化处理确保测试结果准确可靠

Java JMH 代码示例

下面是一个完整的 JMH 示例,覆盖了常见的基准测试场景,包括方法性能比较、不同参数的影响、吞吐量测试、多线程测试等。

1. 基础设置

首先确保添加 JMH 依赖(Maven 配置):

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.37</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.37</version>
    <scope>provided</scope>
</dependency>

2. 完整示例代码

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

@BenchmarkMode(Mode.AverageTime) // 测试模式:平均执行时间
@OutputTimeUnit(TimeUnit.NANOSECONDS) // 输出时间单位
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) // 预热:3轮,每轮1秒
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试:5轮,每轮1秒
@Fork(1) // 使用1个进程
@State(Scope.Benchmark) // 状态共享范围
public class JMHExampleBenchmark {

    // 测试参数:可以用@Param注解测试不同参数值的影响
    @Param({"10", "100", "1000"})
    public int size;

    private List<Integer> arrayList;
    private List<Integer> linkedList;
    private int[] array;
    private AtomicInteger atomicCounter;
    private ConcurrentHashMap<Integer, Integer> concurrentMap;
    private Random random;

    // 初始化方法,在每个测试前执行
    @Setup
    public void setup() {
        arrayList = new ArrayList<>(size);
        linkedList = new LinkedList<>();
        array = new int[size];
        atomicCounter = new AtomicInteger(0);
        concurrentMap = new ConcurrentHashMap<>();
        random = new Random();

        for (int i = 0; i < size; i++) {
            arrayList.add(i);
            linkedList.add(i);
            array[i] = i;
            concurrentMap.put(i, i);
        }
    }

    // 场景1:ArrayList vs LinkedList 随机访问性能对比
    @Benchmark
    public void arrayListRandomAccess(Blackhole bh) {
        for (int i = 0; i < size; i++) {
            bh.consume(arrayList.get(random.nextInt(size)));
        }
    }

    @Benchmark
    public void linkedListRandomAccess(Blackhole bh) {
        for (int i = 0; i < size; i++) {
            bh.consume(linkedList.get(random.nextInt(size)));
        }
    }

    // 场景2:数组遍历的不同方式性能对比
    @Benchmark
    public int arrayTraversalForLoop() {
        int sum = 0;
        for (int i = 0; i < array.length; i++) {
            sum += array[i];
        }
        return sum;
    }

    @Benchmark
    public int arrayTraversalForEach() {
        int sum = 0;
        for (int value : array) {
            sum += value;
        }
        return sum;
    }

    // 场景3:字符串拼接方式性能对比
    @Benchmark
    public String stringConcatPlus() {
        String result = "";
        for (int i = 0; i < size; i++) {
            result += i;
        }
        return result;
    }

    @Benchmark
    public String stringConcatBuilder() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++) {
            sb.append(i);
        }
        return sb.toString();
    }

    // 场景4:原子操作性能测试
    @Benchmark
    public void atomicIncrement() {
        atomicCounter.incrementAndGet();
    }

    // 场景5:并发Map操作测试
    @Benchmark
    public void concurrentMapPut() {
        int key = random.nextInt(size);
        concurrentMap.put(key, key);
    }

    @Benchmark
    public void concurrentMapGet(Blackhole bh) {
        int key = random.nextInt(size);
        bh.consume(concurrentMap.get(key));
    }

    // 场景6:多线程测试
    @Benchmark
    @Threads(4) // 使用4个线程
    public void multiThreadedIncrement() {
        atomicCounter.incrementAndGet();
    }

    // 场景7:吞吐量测试模式
    @Benchmark
    @BenchmarkMode(Mode.Throughput) // 测试吞吐量(ops/time)
    @OutputTimeUnit(TimeUnit.SECONDS)
    public void throughputTest() {
        // 简单的数学运算
        double result = Math.sin(random.nextDouble()) * Math.cos(random.nextDouble());
        Blackhole.consumeCPU(100); // 模拟一些CPU工作
    }

    // 场景8:测试模式组合
    @Benchmark
    @BenchmarkMode({Mode.AverageTime, Mode.Throughput})
    public void multiModeTest() {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        Blackhole.consumeCPU(50);
    }
}

3. 运行JMH测试

创建一个主类来运行基准测试:

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;

public class JMHRunner {
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JMHExampleBenchmark.class.getSimpleName())
                .forks(1) // 使用1个进程
                .build();

        new Runner(opt).run();
    }
}

4. 关键点说明

  1. @State:定义测试状态,可以共享数据
  2. @Setup:测试前的初始化方法
  3. @Benchmark:标记基准测试方法
  4. Blackhole:防止JVM优化掉无实际效果的操作
  5. @Param:参数化测试,可以测试不同输入规模的影响
  6. @Threads:多线程测试
  7. @BenchmarkMode:可以组合多种测试模式
  8. @Warmup:JVM预热设置,避免冷启动影响结果

5. 典型输出结果

运行后会输出类似这样的结果:

Benchmark                                  (size)   Mode  Cnt      Score      Error  Units
JMHExample.arrayListRandomAccess             100  avgt    5   1234.567 ±   12.345  ns/op
JMHExample.linkedListRandomAccess            100  avgt    5  56789.012 ±  678.901  ns/op
JMHExample.stringConcatPlus                  100  avgt    5  12345.678 ±  123.456  ns/op
JMHExample.stringConcatBuilder               100  avgt    5    123.456 ±    1.234  ns/op
JMHExample.throughputTest                    N/A  thrpt    5  123456.789 ± 1234.567  ops/s

这个示例涵盖了JMH的大多数常见用法,你可以根据需要调整参数和测试场景。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 解决创建springboot后启动报错:Failed to bind properties under‘spring.datasource‘

    解决创建springboot后启动报错:Failed to bind properties under‘spri

    在Spring Boot项目中,application.properties和application.yml是用于配置参数的两种文件格式,properties格式简洁但不支持层次结构,而yml格式支持层次性,可读性更好,在yml文件中,要注意细节,比如冒号后面需要空格
    2024-10-10
  • Java中Set集合遍历的四种方法实现

    Java中Set集合遍历的四种方法实现

    本文主要介绍了Java中Set集合遍历的四种方法实现,包括迭代器遍历、增强型for循环遍历、Stream API遍历以及TreeSet的有序遍历,具有一定的参考价值,感兴趣的可以了解一下
    2025-05-05
  • SpringBoot中的扫描注解使用详解

    SpringBoot中的扫描注解使用详解

    为了使Spring Boot应用程序能够扫描并识别特定或自定义注解,通常的做法是在创建这些注解之后,在配置类中通过@ComponentScan指定基础包路径,并设置过滤条件来包含所需的注解,本文给大家介绍了Spring Boot中的扫描注解如何使用,需要的朋友可以参考下
    2025-01-01
  • springboot(thymeleaf)中th:field和th:value的区别及说明

    springboot(thymeleaf)中th:field和th:value的区别及说明

    这篇文章主要介绍了springboot(thymeleaf)中th:field和th:value的区别及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • mybaits中if条件中怎样判断布尔值

    mybaits中if条件中怎样判断布尔值

    这篇文章主要介绍了mybaits中if条件中怎样判断布尔值问题,具有很好的参考价值,希望对大家有所帮助,
    2023-08-08
  • RocketMQ事务消息使用与原理详解

    RocketMQ事务消息使用与原理详解

    这篇文章主要为大家介绍了RocketMQ事务消息的实现原理,在分布式事务解决方案中,事务消息也是一个不错的解决方案,本篇文章将围绕RocketMQ的事务消息实现展开描述,需要的朋友可以参考下
    2023-07-07
  • Spring Boot Admin管理监控数据的方法

    Spring Boot Admin管理监控数据的方法

    本篇文章主要介绍了Spring Boot Admin管理监控数据的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-12-12
  • IDEA Debug过程中使用Drop Frame或Reset Frame实现操作回退的方法

    IDEA Debug过程中使用Drop Frame或Reset Frame实现操作回退的方法

    在IDEA中就提供了一个帮助你回退代码的机会,但这个方法并不是万能的,好了,下面就来具体说说IDEA Debug过程中使用Drop Frame或Reset Frame实现操作回退的方法,感兴趣的朋友一起看看吧
    2022-04-04
  • SpringBoot如何集成Kaptcha验证码

    SpringBoot如何集成Kaptcha验证码

    本文介绍了如何在Java开发中使用Kaptcha生成验证码的功能,包括在pom.xml中配置依赖、在系统公共配置类中添加配置、在控制器中添加生成验证码的方法,以及前端页面如何引用,同时,还补充了Kaptcha的更多配置属性及其默认值
    2025-01-01
  • Spring Boot最经典的20道面试题你都会了吗

    Spring Boot最经典的20道面试题你都会了吗

    Spring Boot是现代化的Java应用程序开发框架,具有高度的灵活性和可扩展性,下面这篇文章主要给大家介绍了关于Spring Boot最经典的20道面试题,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-06-06

最新评论