V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
barcke
V2EX  ›  Java

Java JMH 学习

  •  
  •   barcke · 12 天前 · 674 次点击

    JMH 是什么?

    JMH ( Java Microbenchmark Harness )是一款专用于代码微基准测试的工具集,其主要聚焦于方法层面的基准测试,精度可达纳秒级别。此工具由 Oracle 内部负责实现 JIT 的杰出人士编写,他们对 JIT 及 JVM 在基准测试方面的影响有着深刻的理解。JMH 不仅能够对 Java 语言进行基准测试,还能对运行在 JVM 上的其他语言开展基准测试。 当热点方法被确定,且希望进一步提升方法性能时,可借助 JMH 对优化后的结果进行量化分析。 JMH 具有一些典型的应用场景,如:

    • 精准了解某个方法的执行时间,以及执行时间与输入的相关性;
    • 对比接口不同实现方式在特定条件下的吞吐量;
    • 查看在特定时间段内完成的请求所占比例等。

    JMH 在 JDK9 中是自带的 在 JDK9 之前 我们需要主动的引入下 maven 包

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

    让我们来些第一个 JMH 测试代码吧!

    package cn.ideamake.im.auth.service;
    
    import org.openjdk.jmh.annotations.Benchmark;
    import org.openjdk.jmh.annotations.BenchmarkMode;
    import org.openjdk.jmh.annotations.Fork;
    import org.openjdk.jmh.annotations.Level;
    import org.openjdk.jmh.annotations.Measurement;
    import org.openjdk.jmh.annotations.Mode;
    import org.openjdk.jmh.annotations.OutputTimeUnit;
    import org.openjdk.jmh.annotations.Param;
    import org.openjdk.jmh.annotations.Scope;
    import org.openjdk.jmh.annotations.Setup;
    import org.openjdk.jmh.annotations.State;
    import org.openjdk.jmh.annotations.Threads;
    import org.openjdk.jmh.annotations.Warmup;
    import org.openjdk.jmh.results.format.ResultFormatType;
    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 com.google.common.collect.Lists;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author Barcke
     * @version 1.0
     * @projectName im-auth
     * @className Test
     * @date 2024/4/30 16:30
     * @slogan: 源于生活 高于生活
     * @description:
     **/
    
    // 用来配置 Mode 选项,可用于类或者方法上,这个注解的 value 是一个数组,可以把几种 Mode 集合在一起执行 比如 @BenchmarkMode({Mode.AverageTime, Mode.SampleTime})
    @BenchmarkMode(
            // 每一次方法执行用的平均时间,每次操作的平均时间,单位为 time/op
    //        Mode.AverageTime
            // 随机取样,最后输出取样结果的分布
    //        Mode.SampleTime
            // 只运行一次,往往同时把 Warmup 次数设为 0 ,用于测试冷启动时的性能
    //        Mode.SingleShotTime
            // 吞吐量,每秒执行了多少次调用,单位为 ops/time
            Mode.Throughput
            // 上面的所有模式都执行一次
    //        Mode.All
    )
    // 预热所需要配置的一些基本测试参数,可用于类或者方法上。一般前几次进行程序测试的时候都会比较慢,所以要让程序进行几轮预热,保证测试的准确性。
    @Warmup(
            // 预热次数
            iterations = 3
            // 每次预热的时间
            , time = 1
            // 时间单位,默认秒
            , timeUnit = TimeUnit.SECONDS
            // 批处理大小,每次操作调用几次方法
            , batchSize = 100
    )
    @Measurement(iterations = 5, time = 5)
    // 每个测试线程数量,可用于类或者方法上。
    @Threads(5)
    // 进行 fork 的次数,可用于类或者方法上。如果 fork 数是 3 的话,则 JMH 会 fork 出三个进程来进行测试。
    @Fork(3)
    // 指定一个对象的作用范围,JMH 根据 scope 来进行实例化和共享操作。 @State 可以被继承使用,如果父类定义了该注解,子类则无需定义。由于 JMH 允许多线程同时执行测试
    @State(
            // 默认是 Scope.Benchmark 所有测试线程共享一个实例,测试有状态实例在多线程共享下的性能
    //        value = Scope.Benchmark
            // 线程作用域,每个线程都有自己的实例,每个线程的实例相互独立,不会相互影响
            value = Scope.Thread
            // 线程组作用域,线程组内的线程共享一个实例,线程组外的线程不会共享实例,每个线程的实例相互独立,不会相互影响
    //        value = Scope.Group
    )
    // 结果输出时间单位 可用于类或者方法注解
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    public class Test {
    
    
        private final String KEY = "barcke";
        private List<String> arrayList;
    
        private List<String> linkedList;
    
        // 测试参数
        @Param(value = {"10", "20", "30"})
        private int length;
    
        // @Setup 测试初始化方法 对应在执行测试方法之后调用的可以使用注解 @TearDown 声明
        @Setup(
                // 由于我们设置 Warmup 和 Measurement 的不同,因此每一个基准测试方法都会被执行若干个批次,如果想要在每一个基准测试批次执行的前后调用方法,则可以将 Level 设置为 Iteration 。
    //            Level.Iteration
                // 意味着在每一个批次的度量过程中,每一次对基准方法的调用前都会执行套件方法。
    //            Level.Invocation
                // Setup 和 TearDown 默认的配置,该套件方法会在每一个基准测试方法的所有批次执行的前后被执行。
                Level.Trial
        )
        public void setup() {
            arrayList = Lists.newCopyOnWriteArrayList();
            linkedList = new LinkedList<>();
        }
    
        // Benchmark 用于 测试方法声明
        @Benchmark
        public List<String> arrayListAdd() {
            for (int i = 0; i < length; i++) {
                arrayList.add(KEY);
            }
            return arrayList;
        }
    
        // Benchmark 用于 测试方法声明
        @Benchmark
        public List<String> linkedListAdd() {
            for (int i = 0; i < length; i++) {
                linkedList.add(KEY);
            }
            return linkedList;
        }
    
        public static void main(String[] args) throws RunnerException {
            Options opt = new OptionsBuilder()
                    // 要导入的测试类
                    .include(Test.class.getSimpleName())
                    // 输出测试结果的文件
                    .result("result.json")
                    .resultFormat(ResultFormatType.JSON).build();
            new Runner(opt).run();
        }
    
    }
    

    需要进行测试的方法会用 @Benchmark 注解来标识,关于这些注解的详细含义,将在后续内容中进行具体阐述。 在 main()函数中,首先会对测试用例进行相应的配置。通过采用 Builder 模式来配置测试,将配置的各项参数存入 Options 对象中,然后再利用该 Options 对象来构建 Runner 并启动测试。

    使用 Main 方法执行测试

    需注意!!! idea 执行基准测试过程中可能会出现内存泄露的报错:java.lang.OutOfMemoryError: Java heap space 加大 JVM 的内存参数值即可 如: image image

    做好准备工作后,开始运行代码,静待片刻,测试结果就会出炉,下面对结果做一个简单的分析: image

    image

    image 最终结果: image

    可视化 JMH 工具

    JMH Visual Chart: http://deepoove.com/jmh-visual-chart/ JMH Visualizer: https://jmh.morethan.io/

    将 json 文件导入到网站中则可以得到可视化图表数据 image

    本文到此结束,希望对你有帮助~

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2470 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 14:34 · PVG 22:34 · LAX 07:34 · JFK 10:34
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.