JMH:让你的Java程序性能翻倍的神器

news2024/12/30 1:39:48

大家好!今天我要向大家详细介绍JMH(Java Microbenchmark Harness),这个被誉为Java性能测试的利器。无论你是想优化现有的Java代码还是开发新的项目,JMH都能够帮助你准确、可靠地测量和分析代码的性能,让我们一起来探索JMH的神奇之处吧!

一、JMH 简 介

JMH是由OpenJDK团队开发的一款专业的基准测试工具,旨在提供一个可靠的测试框架,帮助Java开发者进行代码性能的评估和优化。

JMH能够对代码进行微基准测试,以提供精确的性能数据,并帮助开发者发现潜在的性能问题。

二、为 什 么 使 用 JMH 

  1. 高度可靠:JMH采用严格的度量方式,能够排除外部干扰因素的影响,提供准确的性能数据。

  2. 直观可视化:JMH提供丰富的测试结果图表和报告生成工具,让性能数据一目了然,方便开发者分析和优化。

  3. 灵活的配置选项:JMH提供多种注解和选项,可以根据需求进行灵活配置,满足不同类型的性能测试需求。

  4. 自动优化:JMH在测试执行过程中会使用Just-In-Time(JIT)编译器进行代码优化,确保测试结果准确无误。

三、JMH 的 使 用 步 骤

  1. 引入JMH依赖:在项目的构建工具中,引入JMH的依赖,例如Maven或Gradle等。【jdk1.8以下,,包括1.8, 1.9+自带】

<dependencies>
    <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>
        <scope>provided</scope>
    </dependency>
</dependencies>

  2. 编写待测试的方法:使用@Benchmark注解标记待测试的方法,确保方法的可重复执行性。

public class MyBenchmark {
    @Benchmark
    public void myMethod() {
        // 待测试的代码逻辑
    }
}
  1. 配置测试选项:使用各种注解来配置基准测试的参数,例如@BenchmarkMode、@Warmup、@Measurement等。

@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(2)
public class MyBenchmark {
    @Benchmark
    public void myMethod() {
        // 待测试的代码逻辑
    }
}
  1. 运行基准测试:编写入口方法,通过mian方法来运行基准测试,生成测试结果。

@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(2)
public class MyBenchmark {
    @Benchmark
    public void myMethod() {
        // 待测试的代码逻辑
    }

    @Benchmark
    public void myMethod1() {
        // 待测试的代码逻辑
    }

    public static void main(String[] args) throws RunnerException {
    Options options = new OptionsBuilder()
            .include(MyBenchmark.class.getSimpleName())
            .forks(1)
            .build();

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

四、常 用 JMH 注 解 和 选 项

  1. @Benchmark:标记待测试的方法,JMH会自动执行和测量该方法。

  2.  @BenchmarkMode:设置基准测试的模式,默认为Mode.Throughput,还有Mode.AverageTime、Mode.SampleTime等模式可选。

  1. @Warmup:设置预热的迭代次数和时间,用于避免测试开始时的JIT编译影响。

  2. @Measurement:设置测量的迭代次数和时间,用于最终的性能测量。

  3. @Fork:设置进行测试的进程数,可以通过多次运行取得平均结果。

  4. @State:定义测试状态类,可以在不同的基准测试方法之间共享状态。

  5. @Setup:注解可以用于初始化操作,我们可以在基准测试执行前进行一些准备工作。

  6. @TearDown: 注解可以用于清理操作,我们可以在基准测试执行后对资源进行释放。

  7. @Param:用于参数化测试,给定一组参数来运行相同的测试方法。


@State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(2)
public class MyBenchmark {

    @Param({"value1", "value2", "value3"})
    public String param;

    @Benchmark
    public void myMethod() {
        // 待测试的代码逻辑
    }

    @Setup
    public void setup() {
        // 进行基准测试前的初始化操作
    }

    @TearDown
    public void teardown() {
        // 进行基准测试后的清理操作
    }
}

上述代码中,我们使用了@State注解来定义了一个测试状态类,以便在不同的基准测试方法间共享状态。

@BenchmarkMode设置了基准测试的模式为吞吐量模式。

@Warmup注解表示预热阶段的迭代次数为3次,每次迭代1秒。

@Measurement注解表示正式性能测试的迭代次数为5次,每次迭代1秒。

@Fork注解表示执行两次测试进程。

在类中,我们定义了一个被@Benchmark注解标记的方法myMethod(),这是我们待测试的方法。

使用@Param注解对param参数进行了参数化测试。

@Setup注解表示在执行基准测试之前进行的初始化操作,

@TearDown注解表示在执行基准测试之后进行的清理操作。

四、案例:比较String 跟 StringBulider 拼接字符串执行效率


/**
 * @author: xrp
 * @date: 2023/10/09/14:11
 * @description
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 4, time = 2)
@Measurement(iterations = 4, time = 5)
@State(Scope.Thread)
@Slf4j
public class JmhTest {


    ServerInfo serverInfo;
    ServerVO finalServerVO;
    EsLoginMonitorRecord esLoginMonitorRecord;

    @Setup
    public void setApplicationContext() {
   
        serverInfo = new ServerInfo();
        serverInfo.setServerId(1);
        serverInfo.setHostName("123");
        serverInfo.setMacCode("123");

        finalServerVO  = new ServerVO();
        finalServerVO.setGroupStr("1");
        finalServerVO.setTagName("123");

        esLoginMonitorRecord = new EsLoginMonitorRecord();
        esLoginMonitorRecord.setUserName("123");
        esLoginMonitorRecord.setType("1232");
        esLoginMonitorRecord.setStatus("123");
        esLoginMonitorRecord.setStatus("123");
        esLoginMonitorRecord.setLoginIp("123");
        esLoginMonitorRecord.setLoginTime("122");
        esLoginMonitorRecord.setCountry("123");
        esLoginMonitorRecord.setProvince("1236");
        esLoginMonitorRecord.setCity("1568");
    }



    @Benchmark
    public void testString() {
        String  result = "";
        for (int i = 0; i < 2000 ; i++) {
            result += "serverId/"+serverInfo.getServerId()
                    +",serverIp/"+serverInfo.getServerIp()
                    +",hostName/"+serverInfo.getHostName()
                    +",macCode/"+serverInfo.getMacCode()
                    +",groupName/"+StringUtils.defaultString(finalServerVO.getGroupStr())
                    +",tagName/"+StringUtils.defaultString(finalServerVO.getTagName())
                    +",hostRemark/"+StringUtils.defaultString(serverInfo.getRemark())
                    +",userName/"+esLoginMonitorRecord.getUserName()
                    +",type/"+esLoginMonitorRecord.getType()
                    +",status/"+esLoginMonitorRecord.getStatus()
                    +",loginIp/"+esLoginMonitorRecord.getLoginIp()
                    +",loginTime/"+esLoginMonitorRecord.getLoginTime()
                    +",country/"+esLoginMonitorRecord.getCountry()
                    +",province/"+esLoginMonitorRecord.getProvince()
                    +",city/"+esLoginMonitorRecord.getCity();
        }
    }
    

    @Benchmark
    public void testStringBuilder() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 2000 ; i++) {
            sb.append("serverId/").append(serverInfo.getServerId())
                    .append(",serverIp/").append(serverInfo.getServerIp()).append("(").append(serverInfo.getServerLocalIp()).append(")")
                    .append(",hostName/").append(serverInfo.getHostName())
                    .append(",macCode/").append(serverInfo.getMacCode())
                    .append(",groupName/").append(StringUtils.defaultString(finalServerVO.getGroupStr()))
                    .append(",tagName/").append(StringUtils.defaultString(finalServerVO.getTagName()))
                    .append(",hostRemark/").append(StringUtils.defaultString(serverInfo.getRemark()))
                    .append(",userName/").append(esLoginMonitorRecord.getUserName())
                    .append(",type/").append(esLoginMonitorRecord.getType())
                    .append(",status/").append(esLoginMonitorRecord.getStatus())
                    .append(",loginIp/").append(esLoginMonitorRecord.getLoginIp())
                    .append(",loginTime/").append(esLoginMonitorRecord.getLoginTime())
                    .append(",country/").append(esLoginMonitorRecord.getCountry())
                    .append(",province/").append(esLoginMonitorRecord.getProvince())
                    .append(",city/").append(esLoginMonitorRecord.getCity());
        }
    }

    public static void main(String[] args) throws RunnerException {
        Options optionsBuilder = new OptionsBuilder()
                .include(JmhTest.class.getSimpleName())
                .result("L:\\1.json")
                .resultFormat(ResultFormatType.JSON)
                .forks(1)
                .build();
        new Runner(optionsBuilder).run();
    }
}

在案例中可以看到先用了 @Setup 注解初始化了一些数据 提供测试类使用 @BenchmarkMode(Mode.AverageTime)  采用的是计算平均耗时模式,单位毫秒 @OutputTimeUnit(TimeUnit.MILLISECONDS)   看看输出结果

在结果输出方式中,我增加了一个json文件进行输出数据,我们可以把json文件上传到分析网站,可以更加直观的得出结论

分析网站 http://deepoove.com/jmh-visual-chart/ 

可以看出,使用String进行直接拼接大量字符串性能是非常差的,这就是为什么要求使用StringBuilder的原因

六、总结

JMH是一款功能强大的Java性能测试工具,它能够帮助开发者准确、可靠地评估代码的性能,并为性能优化提供参考。

它的使用简单灵活,提供了丰富的注解和选项供开发者配置。

通过使用JMH,我们可以更好地了解代码的性能瓶颈,并进行相应的优化。让我们拿起JMH这个“性能宝剑”,驰骋在Java的性能战场,开创出更优秀的Java应用!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1113275.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

复制交易为什么用经纪商信号?anzo capital昂首资本3点理由心服口服

为什么那么多成功的交易者喜欢复制经纪商的信号进行交易呢&#xff1f;anzo capital昂首资本认为这种模式具有以下优势&#xff1a; 首先&#xff0c;复制信号是免费的&#xff0c;投资者无需支付任何费用即可享受到信号提供商的交易策略。 其次&#xff0c;交易员的排名是透…

双指针——移动零

一&#xff0c;题目要求&#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0…

零代码编程:用ChatGPT下载lexfridman的所有播客音频和文本

莱克斯弗里德曼&#xff08;Lex Fridman&#xff09;&#xff0c;男&#xff0c;麻省理工学院&#xff08;MIT&#xff09;研究科学家兼播客节目主持人&#xff0c;是一位俄罗斯裔美国计算机科学家。2014年&#xff0c;弗里德曼加入谷歌&#xff0c;但在6个月后离开了公司。201…

苹果电脑如何修改文件创建时间

如果您想修改 Mac 上的文件创建时间&#xff0c;可以采用几种不同的方法。您可以使用 Finder 修改文件创建时间&#xff0c;也可以使用终端修改文件创建时间。当然&#xff0c;您还可以使用第三方应用软件进行修改文件创建时间。 小编比较懒&#xff0c;不喜欢太麻烦的操作&am…

苹果手机怎么隐藏照片?(详细图文教程)

我们的手机相册中可能会保存一些与个人隐私相关的照片&#xff0c;比如&#xff1a;银行卡、身份证、护照等私人信息。这些照片如果不进行加密处理的话&#xff0c;会很容易泄露出去。 在别人使用您的手机时&#xff0c;如果您不想这些隐私照片被人看到该怎么办呢&#xff1f;…

Java基础练习(矩阵的加减乘除运算)

简介 对于有了解&#xff0c;但是了解不深的同学&#xff0c;学习Java总是感觉一看就会&#xff0c;一些就废。往往需要一些实操练习&#xff0c;来夯实我们的学习结果。九九乘法表和计算器都是在编程学习领域比较经典的案例。本文为大家讲解一下两个基础实操&#xff0c;熟悉一…

在线JSON转EXCEL工具

全天下的柔情共十分&#xff0c;你占八分。你喊我名字那晚的凉风秋月算一分&#xff0c;其余所有占一分。 推荐 在线JSON转Excel工具 - WeJSON 工具简介 在线JSON转Excel工具&#xff0c;可以快速将JSON数组数据一键转换为Excel格式&#xff0c;方便数据的可视化和交流。 所…

基于Qt设计的邮件收发管理系统(垃圾邮件识别)

基于Qt设计的邮件收发管理系统(垃圾邮件识别) 一、项目背景 随着互联网的发展,邮件成为人们沟通交流不可或缺的一部分。然而,随之而来的是大量的垃圾邮件和欺诈邮件,给人们的生活造成了很大的困扰和威胁。为了解决这个问题,本文提出了一种基于Qt设计的朴素贝叶斯算法邮件收…

SSL证书品牌 Positive

Positive品牌的SSL证书具有以下优势&#xff1a; 1. 安全性&#xff1a;Positive SSL证书提供强大的加密算法&#xff0c;确保通过网站传输的数据得到保护&#xff0c;防止被未经授权的第三方窃取或篡改。 2. 可信度&#xff1a;Positive SSL证书由全球知名的认证机构颁发&am…

AIGC|一文揭秘如何利用MYSCALE实现高效图像搜索?

图像搜索已成为一种流行且功能强大的能力&#xff0c;使用户能够通过匹配功能或视觉内容来查找相似的图像。随着计算机视觉和深度学习的快速发展&#xff0c;这种能力得到了极大的增强。 本文主要介绍如何基于矢量数据库MYSCALE来实现图像搜索功能。 一、MySCALE简介 MyScale 是…

win10下yolov6 tensorrt模型部署

TensorRT系列之 Win10下yolov8 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov8 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov7 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov6 tensorrt模型加速部署 TensorRT系列之 Linux下 yolov5 tensorrt模型加速部署…

uniapp\ taro 如何使用 UnoCSS 原子化css

unocss-preset-weapp 相较于 tailwindcss 和 windicss, unocss的性能和可扩展性都优于它们 UnoCSS小程序预设 unocss-preset-weapp 内置 transformer 用于兼容小程序 transformerClass 转换转义类名&#xff0c;解决小程序不支持\\&#xff0c;\:&#xff0c;\[&#xff0c;…

瞬态抑制二极管TVS的工作原理?|深圳比创达电子EMC(下)

TVS二极管与Zener二极管同样作为过压保护&#xff0c; TVS着重浪涌电压的钳位保护&#xff0c;具有抗大电流冲击的能力;Zener管着重于稳压效果&#xff0c;具有浪涌电流小&#xff0c;保护电压稳的特点&#xff0c;两者在原理与保护特性有所区别&#xff0c;同时在个别应用领域…

回收站删除的文件怎么恢复?快来学习这3个方法!

“有时候我会先将一些不重要的文件放入回收站中&#xff0c;等需要用到时再将它还原。但是我刚刚不小心把回收站清空了&#xff0c;还有方法可以帮我找回我的数据吗&#xff1f;” 在使用电脑时&#xff0c;如果我们删除文件&#xff0c;这些文件会被先收入回收站中。如果我们是…

MODBUS-TCP转MODBUS-RTU通信应用(S7-1200和串口服务器通信)

在学习本博客之前,大家需要熟悉MODBUS-TCP和MODBUS-RTU通信,这2个通信的编程应用,大家可以查看下面文章链接: MODBUS-RTU通信 MODBUS-RTU通信协议功能码+数据帧解读(博途PLC梯形图代码)-CSDN博客MODBUS通信详细代码编写,请查看下面相关链接,这篇博客主要和大家介绍MODB…

商用工程运输车辆智能交通精细数字化管理中的大数据应用

在物联网蓬勃发展的今天&#xff0c;智能交通、智能工业控制、远程控制汽车基本不是什么难题&#xff0c;在集团、企业、都会涉及用车及安全管理&#xff0c;就拿车队管理系统来说&#xff0c;它的主要作用是进行车辆定位、机械诊断、驾驶员行为监测等&#xff0c;并能及时发现…

ubuntu18.04 RTX3060 rangnet++训练 bonnetal语义分割

代码链接&#xff1a; https://github.com/PRBonn/lidar-bonnetal 安装anaconda环境为 CUDA 11.0&#xff08;11.1也可以&#xff09; anaconda环境如下 numpy1.17.2 torchvision0.2.2 matplotlib2.2.3 tensorflow1.13.1 scipy0.19.1 pytorch1.7.1 vispy0.5.3 opencv_python…

工业高频读写器和超高频读写器怎么选?

在工业领域中&#xff0c;RFID技术是常用的信息采集和传输的方式之一&#xff0c;RFID根据频段还可以分为低频、高频和超高频等&#xff0c;在选择具体的频段应用时&#xff0c;很多企业却毫无头绪。接下来本文就高频RFID和超高频RFID读写器的特点进行分析&#xff0c;帮助企业…

Python综合练习题

题目 创建一个系统&#xff0c;里面可以添加学生、添加班级、查看班级里的学生&#xff0c;在控制台输出 效果图 关键代码 完整代码 # -*- coding: UTF-8 -*-#功能 Functionality0 #学生 Student [刘榕榕0, 秦英姿1, 王家乐0, 孟德赫3, 门子伟4, 明展宇5] #班级 Class [大…

景联文数据标注平台助力标注效率翻倍,年处理图像数据过亿

图像标注是指为图像添加文字描述或标签&#xff0c;包括物体、场景、情感、活动等多种内容&#xff0c;以帮助计算机更好地理解和处理图像信息的过程&#xff0c;提高图像处理的自动化程度和准确性&#xff0c;常用于计算机视觉、图像识别等领域。 选择图像标注工具时&#xff…