Java性能调优杀手锏JMH

news2024/11/15 19:52:50

JMH简介

JMH(Java Microbenchmark Harness)由 OpenJDK/Oracle 里面那群开发了 Java编译器的大牛们所开发,是一个功能强大、灵活的工具,它可以用于检测和评估Java应用程序的性能,主要目的是测量Java应用程序的性能,尤其是在多线程环境下的性能。它使用自动生成的测试用例,来测量应用程序的性能,而不是简单的测量方法的性能。

为什么说是微基准测试工具(Micro Benchmark) 呢?

因为是使用在method 层面上,精度可以精确到微秒级。

应用场景:

1、想准确地知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性
2、对比一个函数不同实现在给定条件下的吞吐量(例如 List接口有ArrayList和LinkedList实现),不知道哪种实现性能更好
3、对热点函数进行进一步的优化时,可以使用 JMH 对优化的效果进行定量的分析

快速使用

依赖:

<dependency>
      <groupId>org.openjdk.jmh</groupId>
      <artifactId>jmh-core</artifactId>
      <version>1.27</version>
  </dependency>

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

编写基准测试

测试String+ 和 StringBuilder.append() 两种字符串拼接哪个耗时更短,具体代码如下所示:

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.*;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 3)
@Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS)
@Threads(8)
@Fork(2)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class TestStringBenchmark {

    @Benchmark
    public void string() {
        String s = "";
        for (int i = 0; i < 10; i++) {
            s += i;
        }
    }

    @Benchmark
    public void stringBuilder() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 10; i++) {
            sb.append(i);
        }
    }

    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder()
            .include(TestStringBenchmark.class.getSimpleName())
            .output("D:/benchmark.log")
            .result("D:/jmh_result.json")
            .resultFormat(ResultFormatType.JSON)
            .build();
        new Runner(options).run();
    }

}
TestStringBenchmark
# JMH version: 1.27
# VM version: JDK 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM, 25.91-b15
# VM invoker: F:\Java\jdk1.8.0_91\jre\bin\java.exe
# VM options: -Dvisualvm.id=1830274449076800 -javaagent:F:\IntelliJ IDEA 2021.1.2\lib\idea_rt.jar=54701:F:\IntelliJ IDEA 2021.1.2\bin -Dfile.encoding=UTF-8
# JMH blackhole mode: full blackhole + dont-inline hint
# Warmup: 3 iterations, 10 s each
# Measurement: 10 iterations, 5 s each
# Timeout: 10 min per iteration
# Threads: 8 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: TestStringBenchmark.string

# Run progress: 0.00% complete, ETA 00:05:20
# Fork: 1 of 2
# Warmup Iteration   1: 11945.156 ops/ms
# Warmup Iteration   2: 15712.330 ops/ms
# Warmup Iteration   3: 14511.393 ops/ms
Iteration   1: 14799.601 ops/ms
Iteration   2: 14200.953 ops/ms
Iteration   3: 15198.794 ops/ms
Iteration   4: 14358.358 ops/ms
Iteration   5: 14782.530 ops/ms
Iteration   6: 14986.920 ops/ms
Iteration   7: 15457.126 ops/ms
Iteration   8: 14530.070 ops/ms
Iteration   9: 14836.293 ops/ms
Iteration  10: 14289.823 ops/ms

# Run progress: 25.00% complete, ETA 00:04:12
# Fork: 2 of 2
# Warmup Iteration   1: 13425.736 ops/ms
# Warmup Iteration   2: 14322.458 ops/ms
# Warmup Iteration   3: 15806.225 ops/ms
Iteration   1: 16404.253 ops/ms
Iteration   2: 16489.533 ops/ms
Iteration   3: 15394.679 ops/ms
Iteration   4: 16249.505 ops/ms
Iteration   5: 16780.925 ops/ms
Iteration   6: 16232.516 ops/ms
Iteration   7: 15428.030 ops/ms
Iteration   8: 15868.765 ops/ms
Iteration   9: 15610.369 ops/ms
Iteration  10: 15852.798 ops/ms


Result "TestStringBenchmark.string":
  15387.592 ±(99.9%) 683.453 ops/ms [Average]
  (min, avg, max) = (14200.953, 15387.592, 16780.925), stdev = 787.065
  CI (99.9%): [14704.139, 16071.045] (assumes normal distribution)


# JMH version: 1.27
# VM version: JDK 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM, 25.91-b15
# VM invoker: F:\Java\jdk1.8.0_91\jre\bin\java.exe
# VM options: -Dvisualvm.id=1830274449076800 -javaagent:F:\IntelliJ IDEA 2021.1.2\lib\idea_rt.jar=54701:F:\IntelliJ IDEA 2021.1.2\bin -Dfile.encoding=UTF-8
# JMH blackhole mode: full blackhole + dont-inline hint
# Warmup: 3 iterations, 10 s each
# Measurement: 10 iterations, 5 s each
# Timeout: 10 min per iteration
# Threads: 8 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: TestStringBenchmark.stringBuilder

# Run progress: 50.00% complete, ETA 00:02:47
# Fork: 1 of 2
# Warmup Iteration   1: 92947.764 ops/ms
# Warmup Iteration   2: 44994.076 ops/ms
# Warmup Iteration   3: 44235.632 ops/ms
Iteration   1: 34708.010 ops/ms
Iteration   2: 33766.341 ops/ms
Iteration   3: 28813.465 ops/ms
Iteration   4: 30891.785 ops/ms
Iteration   5: 38159.050 ops/ms
Iteration   6: 38445.872 ops/ms
Iteration   7: 40228.793 ops/ms
Iteration   8: 43060.997 ops/ms
Iteration   9: 40186.636 ops/ms
Iteration  10: 42147.155 ops/ms

# Run progress: 75.00% complete, ETA 00:01:23
# Fork: 2 of 2
# Warmup Iteration   1: 84987.092 ops/ms
# Warmup Iteration   2: 43920.167 ops/ms
# Warmup Iteration   3: 48326.564 ops/ms
Iteration   1: 44952.072 ops/ms
Iteration   2: 46982.241 ops/ms
Iteration   3: 41305.765 ops/ms
Iteration   4: 44203.234 ops/ms
Iteration   5: 47615.004 ops/ms
Iteration   6: 45715.135 ops/ms
Iteration   7: 47256.792 ops/ms
Iteration   8: 45201.375 ops/ms
Iteration   9: 47769.656 ops/ms
Iteration  10: 48446.133 ops/ms


Result "TestStringBenchmark.stringBuilder":
  41492.776 ±(99.9%) 5059.358 ops/ms [Average]
  (min, avg, max) = (28813.465, 41492.776, 48446.133), stdev = 5826.364
  CI (99.9%): [36433.418, 46552.133] (assumes normal distribution)


# Run complete. Total time: 00:05:34

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark                           Mode  Cnt      Score      Error   Units
TestStringBenchmark.string         thrpt   20  15387.592 ±  683.453  ops/ms
TestStringBenchmark.stringBuilder  thrpt   20  41492.776 ± 5059.358  ops/ms

Benchmark result is saved to D:/jmh_result

Process finished with exit code 0

根据测试结果说明,在拼接字符次数越多的情况下,StringBuilder.append() 的性能明显更优秀。

jar 包执行

对于一些小测试,直接用上面的方式写一个 main 函数手动执行就好了。
对于大型的测试,需要测试的时间比较久、线程数比较多,加上测试的服务器需要,一般要放在 Linux 服务器里去执行。

JMH 官方提供了生成 jar 包的方式来执行。
第一步: maven 里增加一个 plugin,具体配置如下:

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.4.1</version>
        <executions>
            <execution>
                <phase>package</phase>
                <goals>
                    <goal>shade</goal>
                </goals>
                <configuration>
                    <finalName>jmh-demo</finalName>
                    <transformers>
                        <transformer
                                implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                            <mainClass>org.openjdk.jmh.Main</mainClass>
                        </transformer>
                    </transformers>
                </configuration>
            </execution>
        </executions>
    </plugin>
</plugins>

或执行

mvn archetype:generate \
         -DinteractiveMode=false \
         -DarchetypeGroupId=org.openjdk.jmh \
         -DarchetypeArtifactId=jmh-java-benchmark-archetype \
         -DgroupId=org.sample \
         -DartifactId=test \
         -Dversion=1.0

第二步:执行 maven 的命令生成可执行 jar 包并执行:

mvn clean install
java -jar target/jmh-demo.jar TestStringBenchmark 

IDEA安装JMH 插件

在 IDEA 中点击 File->Settings…->Plugins
在这里插入图片描述

JMH插件与JUnit 有相同的使用方式,主要功能如下:

自动生成带有 @Benchmark 的方法
像 JUnit 一样,运行单独的 Benchmark 方法
运行类中所有的Benchmark 方法

使用右键点击 Generate
在这里插入图片描述

JMH可视化

图形化网站:
将json 文件导入,就可以实现可视化。
JMH Visual Chart:

http://deepoove.com/jmh-visual-chart/
在这里插入图片描述

JMH Visualizer:

https://jmh.morethan.io/
在这里插入图片描述在这里插入图片描述

注解

@BenchmarkMode

用来配置 Mode 选项,可用于类或者方法上,这个注解的 value 是一个数组,可以把几种 Mode 集合在一起执行,如:@BenchmarkMode({Mode.SampleTime, Mode.AverageTime}),还可以设置为 Mode.All,即全部执行一遍。

Throughput:整体吞吐量,每秒执行了多少次调用,单位为 ops/time
AverageTime:用的平均时间,每次操作的平均时间,单位为 time/op
SampleTime:随机取样,最后输出取样结果的分布
SingleShotTime:只运行一次,往往同时把 Warmup 次数设为 0,用于测试冷启动时的性能
All:上面的所有模式都执行一次

@State

通过 State 可以指定一个对象的作用范围,JMH 根据 scope 来进行实例化和共享操作。@State 可以被继承使用,如果父类定义了该注解,子类则无需定义。由于 JMH 允许多线程同时执行测试,不同的选项含义如下:

Scope.Benchmark:所有测试线程共享一个实例,测试有状态实例在多线程共享下的性能
Scope.Group:同一个线程在同一个 group 里共享实例
Scope.Thread:默认的 State,每个测试线程分配一个实例

@OutputTimeUnit

为统计结果的时间单位,可用于类或者方法注解

@Warmup

预热所需要配置的一些基本测试参数,可用于类或者方法上。一般前几次进行程序测试的时候都会比较慢,所以要让程序进行几轮预热,保证测试的准确性。参数如下所示:

iterations:预热的次数
time:每次预热的时间
timeUnit:时间的单位,默认秒
batchSize:批处理大小,每次操作调用几次方法

为什么需要预热?

因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译为机器码,从而提高执行速度,所以为了让
benchmark 的结果更加接近真实情况就需要进行预热。

@Measurement

实际调用方法所需要配置的一些基本测试参数,可用于类或者方法上,参数和 @Warmup 相同。

@Threads

每个进程中的测试线程,可用于类或者方法上。

@Fork

进行 fork 的次数,可用于类或者方法上。如果 fork 数是 2 的话,则 JMH 会 fork 出两个进程来进行测试。

@Param

指定某项参数的多种情况,特别适合用来测试一个函数在不同的参数输入的情况下的性能,只能作用在字段上,使用该注解必须定义@State 注解。

小结

JMH是一款强大的Java和JVM性能基准测试工具,它能够准确、灵活地测量各种Java应用程序的性能,并通过可视化界面帮助开发人员快速定位性能瓶颈。有了JMH,开发人员就能够更加精确、有效地调优Java应用程序的性能,从而提高应用程序的效率和稳定性。

参考资料

https://www.oracle.com/technical-resources/articles/java/architect-benchmarking.html
https://github.com/lexburner/JMH-samples
https://www.cnkirito.moe/java-jmh/
https://openjdk.org/projects/code-tools/jmh/
在这里插入图片描述

人生而自由,却无往不在枷锁之中。

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

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

相关文章

SpringBoot 中的 Aop + 自定义注解(内含源代码)

SpringBoot 中的 Aop 自定义注解&#xff08;内含源代码&#xff09; 源代码下载链接地址&#xff1a;https://download.csdn.net/download/weixin_46411355/87553544 目录SpringBoot 中的 Aop 自定义注解&#xff08;内含源代码&#xff09;源代码下载链接地址&#xff1a;…

Docker WIndows最新版(4.17.x)修改镜像存储路径

一般情况 在 Docker Desktop 4.17.0 版本中&#xff0c;可以按照以下步骤来修改 Docker 镜像存储路径 打开 Docker Desktop 应用程序&#xff0c;单击顶部菜单栏中的 Docker Desktop 菜单&#xff0c;然后选择 Resources&#xff08;资源&#xff09;选项卡。 在 Resources 选…

【微信小程序】-- 自定义组件 -- 创建与引用 样式(三十二)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…

信创国产化替代势在必行,FTP有替代产品吗?

自2018年开始&#xff0c;由于国外对IT技术发展的限制&#xff0c;在“华为、中兴事件”催化下&#xff0c;我国信创进入快速推广期。信创的产业庞大&#xff0c;涉及基础硬件&#xff08;芯片、服务器等&#xff09;、基础软件&#xff08;操作系统、数据库、中间件等&#xf…

QT使用QListWidget显示多张图片

Qt系列文章目录 文章目录Qt系列文章目录前言一、QListWidget 和 QListView 的差异二、显示效果1.操作工作区界面1.主界面头文件2. 主界面实现界面2.左边图片目录展示界面1.图片目录头文件2.图片目录实现文件2.属性窗口区1.属性窗口头文件2.属性窗口实现文件3 源码下载前言 QLi…

【安卓软件】KMPlayer-一款完美的媒体播放器 可以播放所有格式的字幕和视频

KM PlayerKM Player是一款未编码的视频播放器&#xff0c;让您无需编码即可方便地播放各种格式的视频&#xff0c;并为您的新体验添加了字幕支持、视频播放速度和手势等功能。KMPlayer 拥有美观和直观的设计&#xff0c;让您可以更方便地管理和播放视频&#xff01;功能高品质视…

电流分段校准原理与步骤

分段电流校准是一种校准电流传感器的方法。 传感器不可避免地存在误差&#xff0c;这些误差可能来自于不同方面&#xff0c;例如温度漂移、零点漂移和灵敏度漂移等。 在精确测量电流的应用中&#xff0c;这些误差可能会导致测量结果不准确。 因此&#xff0c;对电流传感器进行校…

100种思维模型之认知资源思思维模型-030

我们常说&#xff0c;一个人永远也赚不到自己认知以外的钱&#xff0c;这话的确很有道理&#xff0c;被无数人所推崇。 由此&#xff0c;不难看出&#xff0c;认知在我们的生活起着多么关键的作用。 你的认知层次越高&#xff0c;范围越广&#xff0c;就意味着你这个人所处的阶…

vue - vue使用腾讯api进行定位获取,绘制地图、标点、搜索、路线规划

章节目录1&#xff0c;前言2&#xff0c;准备3&#xff0c;开始引入api4&#xff0c;vue组件中使用 - 获取定位5&#xff0c;绘制地图和标点6&#xff0c;关键字搜索功能7&#xff0c;驾车线路规划8&#xff0c;演示组件中的全部代码9&#xff0c;参考链接1&#xff0c;前言 首…

Vue项目本地开发集成引入https

问题描述 本地项目开发中用到的接口是https &#xff0c;本地http会请求不到数据 案例使用采用的vue-cli开发&#xff0c;所以需要针对这两种方式启动https 问题处理 1.首先是需要配置一个证书,使用mkcert 进行配置证书 2.在vue-cli 中进行修改package.json、vue.config.js 中进…

Leetcode.828 统计子串中的唯一字符

题目链接 Leetcode.828 统计子串中的唯一字符 Rating &#xff1a; 2034 题目描述 我们定义了一个函数 countUniqueChars(s)来统计字符串 s中的唯一字符&#xff0c;并返回唯一字符的个数。 例如&#xff1a;s "LEETCODE"&#xff0c;则其中 "L", "…

【Mysql】查询数据库,行转列,mapper.xml中查询条件的写法

目录 一、用mysql脚本建表二、现有以下三个实体对应三张表&#xff0c;其关联关系如下三、行转列的sql语句四、对应的mapper.xml写法五、输入某一关键字&#xff0c;查找车牌号或车名包含该关键字的车辆用or六、总结&#xff1a;用GROUP_CONCAT实现行转列一、用mysql脚本建表 …

compose系列教程-6.实现图文列表,添加点击事件

每个行添加点击事件&#xff0c;可以使用Clickable组件。在Clickable组件的onClick参数中&#xff0c;您可以指定要在用户单击行时执行的操作。下面是一个示例代码&#xff1a; Composable fun ImageTextList(imageTextList: List<ImageTextItem>, onItemClick: (ImageTe…

机器学习学习记录1:基本术语和假设空间

基本术语机器学习正是这样一门学科&#xff0c;它致力于研究如何通过计算的手段&#xff0c;利用经 验来玫善系统自身的性能在计算机系统中&#xff0c;"经验"通常以"数据"形式存 在&#xff0c;因此&#xff0c;机器学习所研究的主要内容&#xff0c;是关…

数据仓库的设计思想

数据仓库设计 知识点01&#xff1a;设计大纲与学习目标 #内容大纲1、数据仓库基础知识&#xff08;回顾&#xff09;什么是数仓为什么有数仓数仓的特点是什么OLTP和OLAP系统区别&#xff08;数据库和数仓的区别&#xff09;2、数仓系统的架构与核心流程核心1&#xff1a;ETL核…

mybatis(二)

mybatis练习---2种方式 能够使用映射配置文件实现CRUD操作 能够使用注解实现CRUD操作 配置文件CRUD就是把sql语句写到配置文件中&#xff0c;注解CRUD就是吧sql语句写到注解上。 一、配置文件实现CRUD 如上图所示产品原型&#xff0c;里面包含了品牌数据的 查询 、 按条件查…

使用ControlNet 控制 Stable Diffusion

本文将要介绍整合HuggingFace的diffusers 包和ControlNet调节生成文本到图像&#xff0c;可以更好地控制文本到图像的生成 ControlNet是一种通过添加额外条件来控制扩散模型的神经网络结构。它提供了一种增强稳定扩散的方法&#xff0c;在文本到图像生成过程中使用条件输入&…

【工具使用】STM32CubeMX-基础使用篇

一、概述 无论是新手还是大佬&#xff0c;基于STM32单片机的开发&#xff0c;使用STM32CubeMX都是可以极大提升开发效率的&#xff0c;并且其界面化的开发&#xff0c;也大大降低了新手对STM32单片机的开发门槛。     本文主要面向初次接触STM32CubeMX的同学&#xff0c;大…

垃圾回收:垃圾数据如何自动回收

有些数据被使用之后&#xff0c;可能就不再需要了&#xff0c;我们把这种数据称为垃圾数据。如果这些垃圾数据一直保存在内存中&#xff0c;那么内存会越用越多&#xff0c;所以我们需要对这些垃圾数据进行回收&#xff0c;以释放有限的内存空间 不同语言的垃圾回收策略 通常…

「中华田园敏捷开发」,是老板无能还是程序员无力?

敏捷开发一直都是无数程序员的追求&#xff0c;也被被视为“开发者的福音”&#xff0c;但显然敏捷开发在中国落地的专业度还不够&#xff0c;以至于出现了“中华田园敏捷”的说法&#xff0c;什么叫“中华田园敏捷开发”&#xff1f; 简单点说&#xff1a;中华田园敏捷开发的…