如何使用工具提高Java代码的质量(基于Eclipse)-3

评判代码质量, 性能是一个重要的考量。大多数的软件都会对响应时间和吞吐量有要求。对应用程序进行性能测试是检验应用性能的重要手段, 而在编码阶段我们也需要对一些关键或者频繁调用的代码进行性能测试, 来评估自己编写的接口性能; 当对已有的接口实现添加新的业务逻辑时, 我们也需要通过测试来评估变更带来的性能影响; …

我们常规的做法是在需要测试的代码的开始位置记录下开始时间戳(例如通过System.currentTimeMillis()),在结束位置也记录结束时间戳, 通过计算时间差来获得执行时间。更优雅的做法是通过第三方的Commons库, 如Guava的Stopwatch, Apache Commons Lang的StopWath来获取执行时间, 这些做法没有本质不同, 都会遇到一些问题: 首先在最终代码中嵌入了测试代码,破坏原有代码, 影响代码的可读性,也容易带入到最终的程序(当然, 可以放在单元测试代码中解决)。其次, 对于有大量测试需求的场景, 工作量很大,不仅要编写测试代码,还要编写执行测试的代码,效率很低; 更关键的问题在于, 要对Java做一个准确可靠的性能测试并不容易, 难点在于Java的代码的是通过JVM执行的,而JVM的JIT和GC都会对测试结果产生影响,所以我们需要一些测试框架来协助我们。其中一个就是本文的主角JMH(Java Microbenchmark Harness)。

"Micro benchmarking itself is not an easy topic, and doing it correctly using languages like Java is a very difficult 
task. These difficulties arise from the way Java executes the code and the infrastructure required by JVM. Just as 
things like JIT and GC may affect the results of micro benchmarking heavily, to ensure that the result of each run 
is consistent and correct might not be an easy task to accomplish. To help with this problem, there are several frameworks 
that can help to ensure that the benchmark test runs properly. One of these frameworks is Java Microbenchmark 
Harness (JMH), which is a part of OpenJDK."

JMH简介

JMH (https://github.com/openjdk/jmh)是OpenJDK团队开发的一款开源的微基准测试(Microbenchmark)工具,所谓的微基准测试, 是根据测试粒度而言的, 微基准测试以程序的某一段代码(方法)为测试单位, Microbenchmark测试的是代码,而不是程序。Java程序调用的基本单位是方法,所以你也可以认为JMH是基于方法的基准测试。这就意味在编码之初,只要你编写了方法,就可以进行性能测试, 而不需要等到整个程序编码完成,并构建成程序。

和JUnit类似, JMH也使用Java的注解来简化开发。我们只需要在进行基准测试的方法上使用 @Benchmark进行标注, 而JMH就会在编译阶段为该方法自动的生成基准测试代码, 并将该方法注册到基准测试列表中,并根据注解的配置,准备基准测试的执行环境。

JMH is a Java harness for building, running, and analysing nano/micro/milli/macro benchmarks written in Java and other 
languages targeting the JVM. JMH will produce the generated benchmark code for this method during compilation,register 
this method as the benchmark in the benchmark list, read out the defaultvalues from the annotations, and generally prepare 
the environment for the benchmarkto run.

JMH基于Eclipse配置

官方推荐通过Apache Maven创建一个独立的工程来运行JMH基准测试,这样更能确保结果的准确性。在IDE上运行基准测试也是可以的,但是执行环境越复杂,结果越不可靠(more complex and the results are less reliable)。

不管用什么方式执行测试, 测试代码编写还是离不开IDE的。所以简单介绍下如何在Eclipse中配置JMH环境。非常简单,在Eclipse中创建一个Maven项目, archetype选jmh-java-benchmark-archetype即可。
在这里插入图片描述
注意: 如果仓库搜索很慢,另一种方法就是点击"Add Archetype", 直接添加, "Repository URL"可以选国内的镜像站。

Maven中央仓库在国外,如果速度会非常慢,可以配置一个国内的镜像,阿里提供的镜像是其中比较稳定的(https://maven.aliyun.com/mvn/guide),推荐使用。

<!-- ${user.home}/.m2/settings.xml -->
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
	<mirrors>
		<mirror>
			<id>aliyunmaven</id>
			<mirrorOf>*</mirrorOf>
			<name>阿里云公共仓库</name>
			<url>https://maven.aliyun.com/repository/public</url>
		</mirror>
	</mirrors>
</settings>

编写简单的基准测试

只需要在测试方法使用@Benchmark标注即可,其它的都交给JMH。

package org.littlestar.jmh1;

import org.openjdk.jmh.annotations.Benchmark;

/**
 * <pre>
 * 测试字符串拼接的3种不同方式的效率。
 *   stringConcat: 使用"+"运算符进行字符串拼接;
 *   stringAppend1: 使用StringBuilder.append方法拼接;
 *   stringAppend2: 使用StringBuilder.append方法拼接, 并为StirngBuilder指定初始容量;
 * </pre>
 */
public class Benchmark1 {
	@Benchmark
	public String stringConcat() {
		String result = "";
		String appendText = "测试JMH";
		int loopCount = 256;
		for (int i = 0; i < loopCount; i++) {
		    result = result + appendText;
		}
		return result;
	}
	
	@Benchmark
	public String stringAppend1() {
		String appendText = "测试JMH";
		int loopCount = 256;
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < loopCount; ++i) {
		    sb.append(appendText);
		}
		String result = sb.toString();
		return result;
	}
	
	@Benchmark
	public String stringAppend2() {
		String textToAppend = "测试JMH";
		int loopCount = 256;
		StringBuilder sb = new StringBuilder(loopCount * textToAppend.length());
		for (int i = 0; i < loopCount; ++i) {
		    sb.append(textToAppend);
		}
		String result = sb.toString();
		return result;
	}
}

只要简单的使用@Benchmark注解标注基准测试方法即编写完成了一个基准测试,使用非常的简单。

执行基准测试

编写完基准测试,接下来就是执行基准测试。有两种方法执行基准测试:

  1. 通过org.openjdk.jmh.Main类;
  2. 通过JMH Runner类;

从本质上来看,就只有一种方式那就是Runner类,通过org.openjdk.jmh.Main类源码可以看出,该类只包含一个main方法,Main类只是对Runner和测试参数(Options)设置进行了的命令行封装。

通过生成benchmarks.jar执行基准测试

通过mvn install打包成jar文件,并执行该jar文件。
注意:

  • 需要先安装apache maven。
  • 要构建一个可执行的jar文件,所以基准测试代码需要放在src/main/java目录下,而不是放在src/test/java目录下。
# 命令行模式进入Eclipse的项目根目录,执行:
$ mvn clean install

# 生成的测试可执行的jar文件位置为项目目录的target/benchmarks.jar。
# 通过pom.xml配置可以知道,这个jar文件的mainClass就是org.openjdk.jmh.Main

# -h 参数可以查看benchemarks的帮助
$ java -jar target\benchmarks.jar -h

#开始执行基准测试
$ java -jar target\benchmarks.jar

通过自己编写main方法执行基准测试

通过自己编写main方法,通过Runner类的run方法执行,通过Options类设置测试参数。最后执行main方法开始测试。

package org.littlestar.jmh1;
public class Test4 {
	public static void main(String[] args) throws RunnerException {
	       Options opt = new OptionsBuilder()
	                .include(Test1.class.getSimpleName()) 
	                .include(Test2.class.getSimpleName())
	                .mode(Mode.AverageTime)
	                .forks(1)
	                .warmupMode(WarmupMode.BULK)
	                .warmupIterations(2)
	                .warmupTime(TimeValue.milliseconds(1000))
	                .measurementIterations(2)
	                .measurementTime(TimeValue.milliseconds(4000))
	                .build();
	       new Runner(opt).run();
	}
}
// 执行测试:
// C:\Users\Think\eclipse-workspace\jmh1>mvn clean install
// C:\Users\Think\eclipse-workspace\jmh1>java -cp target\* org.littlestar.jmh1.Test4

!!注意,如果你要直接在Eclipse中执行main方法, 你需要先选中你的Maven项目,执行"Run As" -> “Maven clean"和"Run As” -> “Maven install”。在修改测试代码后,Eclipse中执行main方法前,都需要执行这两步操作。另一种方法是通过安装配置"m2e-apt"插件,可以执行研究。

基准测试报告

借由一个基准测试的输出,介绍以下基准测试输出的报告。

C:\Users\Think\eclipse-workspace\jmh1>java -jar target/benchmarks.jar
# JMH version: 1.32
# VM version: JDK 1.8.0_271, Java HotSpot(TM) 64-Bit Server VM, 25.271-b09
# VM invoker: C:\Program Files\Java\jre1.8.0_271\bin\java.exe
# VM options: <none>
# Blackhole mode: full + dont-inline hint
# Warmup: 5 iterations, 10 s each // 正式测试前预热阶段, 执行5次迭代(iteration)过程, 每次迭代过程时间10秒钟, 你可以理解为10秒钟内循环执行被测试方法。
# Measurement: 5 iterations, 10 s each  // 正式测阶段, 执行5次迭代(iteration)过程, 每次迭代过程时间10秒钟
# Timeout: 10 min per iteration  // 每次迭代超时时间为10分钟
# Threads: 1 thread, will synchronize iterations // 一个线程执行迭代
# Benchmark mode: Throughput, ops/time  // 基准测试模式为吞吐量模式(Throughput)
# Benchmark: org.littlestar.jmh1.Benchmark1.stringAppend1 // 被测试的方法为"org.littlestar.jmh1.Benchmark1.stringAppend1" 

# Run progress: 0.00% complete, ETA 00:25:00
# Fork: 1 of 5  // 被测试方法的一次完整的基准测试过程(Warmup + Measurement), 称为一次Fork. 要进行5次测试(默认设置), 这是其中第一次。
# Warmup Iteration   1: 301056.957 ops/s  # 每秒执行的stringAppend1的次数。
# Warmup Iteration   2: 304897.065 ops/s # ...
# Warmup Iteration   3: 310179.013 ops/s
# Warmup Iteration   4: 315793.574 ops/s
# Warmup Iteration   5: 315900.762 ops/s
Iteration   1: 313135.035 ops/s
Iteration   2: 312054.961 ops/s
Iteration   3: 306075.563 ops/s
Iteration   4: 313998.338 ops/s
Iteration   5: 303680.505 ops/s

# Run progress: 6.67% complete, ETA 00:40:31
# Fork: 2 of 5 //stringAppend1的第二次fork..
...

Result "org.littlestar.jmh1.Benchmark1.stringAppend1":
  305767.081 ±(99.9%) 4323.249 ops/s [Average]
  (min, avg, max) = (288185.013, 305767.081, 313998.338), stdev = 5771.414
  CI (99.9%): [301443.832, 310090.330] (assumes normal distribution)
  
// stringAppend1方法测试结束, 测试结果分析: 
平均每秒执行305767.081次,CI(confidence interval),也就是统计学中的置信区间, 也就是说,有99.9%概率确定stringAppend的真实ops/s都会落在[301443.832, 310090.330]区间内。
...
// 进行stringAppend2方法的基准测试...
Result "org.littlestar.jmh1.Benchmark1.stringAppend2":
  485416.377 ±(99.9%) 5639.919 ops/s [Average]
  (min, avg, max) = (468503.172, 485416.377, 496655.486), stdev = 7529.130
  CI (99.9%): [479776.458, 491056.296] (assumes normal distribution)
  
...
// 进行stringConcat方法的基准测试...
Result "org.littlestar.jmh1.Benchmark1.stringConcat":
  17217.956 ±(99.9%) 318.274 ops/s [Average]
  (min, avg, max) = (16018.276, 17217.956, 17717.729), stdev = 424.887
  CI (99.9%): [16899.682, 17536.231] (assumes normal distribution)

// 整个测试执行总时间00:36:31
# Run complete. Total time: 00:36:31,

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
Benchmark1.stringAppend1  thrpt   25  305767.081 ± 4323.249  ops/s
Benchmark1.stringAppend2  thrpt   25  485416.377 ± 5639.919  ops/s
Benchmark1.stringConcat   thrpt   25   17217.956 ±  318.274  ops/s

// 最后3个方法结果汇总, 三个方法基准测试模式都为吞吐量模式(Throughput), 测量(Measurement)总共执行25次迭代测试过程,(每次5次迭代过程)* Fock5次:
  stringAppend2方法的吞吐量最高, 真实可信的ops/s为485416.377 ± 5639.919, 也就是CI (99.9%): [479776.458, 491056.296];
  stringAppend1方法的吞吐量第二, 真实可信的ops/s为305767.081 ± 4323.249, 也就是CI (99.9%): [301443.832, 310090.330]
  stringConcat方法的吞吐量最差,  真实可信的ops/s为 17217.956 ±  318.274, 也就是CI (99.9%): [16899.682, 17536.231]

很酷吧, 你还可以随便找个工具,做成一个图表,更直观(能装)了。
在这里插入图片描述
大量(256次)的字符串拼接(全是字符串常量除外),使用"+"操作的性能是最差的,使用StringBuilder并使用默认的初始容量的性能其次, 因为当StirngBuilder容量不够时, 需要进行多次"扩容"造成了性能的损耗。使用StringBuilder并指定合理的初始容量大小是性能最优的。

设置基准测试

JMH允许我们通过参数来控制基准测试的执行:

  1. 通过benchmarks.jar的命令行参数;
  2. 通过Options类传传递给Runner;
  3. 通过JMH提供的注解(Annotations);

本质上1,2是相同的,都是通过Options类, 传递给Runner, 在优先级上, Options类的优先级高于注解, 如果Options和注解同时设置了同一个参数,以Options类指定的参数值为准, 注解指定的参数值会被覆盖。

通过命令行参数

通过benchmarks.jar执行基准测试,可以通过其命令行参数参数来设置基准测试, 具体用法参考-h帮助。

C:\Users\Think\eclipse-workspace\jmh1>java -jar target\benchmarks.jar -bm AverageTime -f 2 -i 1 -r 4 -wi 1 -w 1
# JMH version: 1.32
# VM version: JDK 1.8.0_291, Java HotSpot(TM) 64-Bit Server VM, 25.291-b10
# VM invoker: D:\Program Files\Java\jdk1.8.0_291\jre\bin\java.exe
# VM options: <none>
# Blackhole mode: full + dont-inline hint
# Warmup: 1 iterations, 1 s each      --> -wi 1 -w 1
# Measurement: 1 iterations, 4 s each  --> -i 1 -r 4
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op  --> -bm AverageTime

通过Options类

如果直接编写main方法来执行基准测试,可以通过Options设置参数,并传递给Runner执行。因为测试选项非常多,所以这里JMH使用了 生成器设计模式(Builder), 我们通常使用OptionsBuilder来简化Options的创建。

	       Options opt = new OptionsBuilder()
	                .include(Test1.class.getSimpleName()) 
	                .include(Test2.class.getSimpleName())
	                .mode(Mode.AverageTime)
	                .forks(1)
	                .warmupMode(WarmupMode.BULK)
	                .warmupIterations(2)
	                .warmupTime(TimeValue.milliseconds(1000))
	                .measurementIterations(2)
	                .measurementTime(TimeValue.milliseconds(4000))
	                .build();
	       new Runner(opt).run();

通过注解(Annotations)

JMH支持通过Annotations来设置如何进行基准测试。

@BenchmarkMode

指定基准测试的模式, JMH支持多种不同的基准测试模式:

模 式说 明
Mode.ThroughputMeasures the number of operations per second, meaning the number of times per second your benchmark method could be executed.
Mode.AverageTimeMeasures the average time it takes for the benchmark method to execute (a single execution).
Mode.SampleTimeMeasures how long time it takes for the benchmark method to execute, including max, min time etc.
Mode.SingleShotTimeMeasures how long time a single benchmark method execution takes to run. This is good to test how it performs under a cold start (no JVM warm up).
Mode.AllMeasures all of the above.
// 用法示例
@Benchmark
@BenchmarkMode(Mode.AverageTime)
public String benchmark1() {
}

@Warmup

一次基准测试包含了预热阶段和实际测量阶段。@Warmup注解可以用于预热阶段的参数。
Warmup annotation allows to set the default warmup parameters for the benchmark.

// 用法示例
@Warmup(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)

@Measurement

一次基准测试包含了预热阶段和实际测量阶段。@Measurement注解可以用于设置实际测量阶段的参数。
Measurement annotations allows to set the default measurement parameters forthe benchmark.

// 用法示例
@Measurement(iterations = 200, time = 200, timeUnit = MILLISECONDS)

@Fork

how many times the benchmark will be executed, and the warmup parameter controls how many times a benchmark will dry run before results are collected

// 用法示例
@Fork(1)
@Fork(value = 2, jvmArgs = {"-Xms2G", "-Xmx2G"})
@Fork(value = 1, warmups = 2)

高级用法

Benchmark State

什么是State(状态), 什么是State变量, 什么是State类。

"Sometimes you way want to initialize some variables that your benchmark code needs, but which you do not want to be part 
of the code your benchmark measures. Such variables are called "state" variables. State variables are declared in special 
state classes, and an instance of that state class can then be provided as parameter to the benchmark method. "

基准测试方法用到定义在其方法之外的变量就称为状态变量,而包含状态变量的类,就称为状态类,为什么要提出找个概念,因为基准测试方法不能直接使用状态变量,而是需要使用State注解, 标注状态类。

public class Test2 {
	final AtomicInteger counter = new AtomicInteger(0);
	@Benchmark
	public int benchmark1() {
		return counter.addAndGet(1);
	}
	...
}
对于JMH,上述代码是无法编译通过的:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.0:compile (default-compile) on project jmh1: Compilation failure
[ERROR] /C:/Users/Think/eclipse-workspace/jmh1/src/main/java/org/littlestar/jmh1/Test2.java:[15,29] Field "counter" is declared within the class not having @State annotation. This can result in unspecified behavior, and prohibited.

benchmark1使用了状态变量counter,那么Test2就是状态类,需要使用@State标注。

@State

@State注解用于标注一个状态类,该注解接受一个 Scope 参数用来表示该状态的共享范围。Scope包括:
Thread: 该状态为每个线程独享。
Group: 该状态为同一个组里面所有线程共享。
Benchmark: 该状态在所有线程间共享。

// 我们只需要使用@State标注就可以正常的编译通过。
@State(Scope.Thread)
public class Test2 {
	...
}

@Setup @TearDown

类似于JUnit,对于状态类,可以定义生命周期方法,来控制变量的初始化(@Setup)和销毁(@TearDown)。这两个注解接收Level参数, 以控制其标注的生命周期方法的粒度:

  Level.Trial:Benchmark级别,每次Fock开始或者结束时执行;
  Level.Iteration:执行迭代级别, 每次迭代开始或者结束时执行;
  Level.Invocation:方法调用级别, 每次基准测试方法调用开始或者结束时执行;

看例子比较好说明:

package org.littlestar.jmh1;

import java.util.concurrent.atomic.AtomicInteger;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
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 org.openjdk.jmh.runner.options.TimeValue;

//@State(Scope.Benchmark)
//@State(Scope.Group)
@State(Scope.Thread)
public class Test2 {
	final AtomicInteger counter = new AtomicInteger(0);
	@TearDown(Level.Iteration)
	public void init() {
	}
	
	@TearDown(Level.Iteration)
	public void close() {
		System.out.print(" [@TearDown: counter = " + counter.get()+"] ");
	}
	
	@Benchmark
	//@Group("my_group_1")
	public int benchmark1() {
		sleep2(100L);
		return counter.addAndGet(1);
	}
	
	@Benchmark
	//@Group("my_group_1")
	public int benchmark2() {
		sleep2(100L);
		return counter.addAndGet(1);
	}
	
	public static void main(String[] args) throws RunnerException {
	       Options opt = new OptionsBuilder()
	    		   .include(Test2.class.getSimpleName())
	    		   .forks(1)
	    		   .threads(2)  
	    		   .warmupIterations(2)
	    		   .warmupTime(TimeValue.milliseconds(500))
	    		   .measurementIterations(2)
	    		   .measurementTime(TimeValue.milliseconds(500))
	    		   .build();
	       new Runner(opt).run();
	}
	
	static void sleep2(long millis) {
		try {
			Thread.sleep(millis);
		} catch (InterruptedException e) {
		}
	}
}

只有多线程(指定threads(n))时候State的不同Scope才会有区别, Scope.Thread时,在每次fock期间每个线程拥有各自独立的状态变量(counter), 而Scope.Benchmark则在每次fock期间每个线程共享状态变量(counter)。

Scope.Group比较特殊, 类似于Scope.Benchmark, 而且迭代时每个Group的成员会做为一个独立的线程同时执行,例如, Options指定了threads=2,有2个测试方法标注为同一个组"my_group_1", 那么会有4个线程同时执行benchmark1和benchmark2测试方法,并且这些线程共享counter状态变量。

Profile

Profile是性能分析的利器,JMH自带丰富的Profile分析工具,可以协助你对测试代码进行进一步的分析。可以通过命令行参数或者Options类来指定需要执行的Profiler。

通过命令行参数

//通过 -lprof列出支持的Profilers
C:\Users\Think\eclipse-workspace\jmh1>java -jar target\benchmarks.jar  -lprof
Supported profilers:
          cl: Classloader profiling via standard MBeans
        comp: JIT compiler profiling via standard MBeans
          gc: GC profiling via standard MBeans
         jfr: Java Flight Recorder profiler
      pauses: Pauses profiler
     perfc2c: Linux perf c2c profiler
  safepoints: Safepoints profiler
       stack: Simple and naive Java stack profiler

Unsupported profilers:
       async: <none>
Unable to load async-profiler. Ensure asyncProfiler library is on LD_LIBRARY_PATH (Linux), DYLD_LIBRARY_PATH (Mac OS), or -Djava.library.path. Alternatively, point to explicit library location with -prof async:libPath=<path>.
   dtraceasm: <none>
[Cannot run program "sudo": CreateProcess error=2, 系统找不到指定的文件。]
        perf: <none>
[Cannot run program "perf": CreateProcess error=2, 系统找不到指定的文件。]
     perfasm: <none>
[Cannot run program "perf": CreateProcess error=2, 系统找不到指定的文件。]
    perfnorm: <none>
[Cannot run program "perf": CreateProcess error=2, 系统找不到指定的文件。]
    xperfasm: <none>
java.lang.reflect.InvocationTargetException

// 通过-prof选项指定, 例如指定使用stack profiler:
C:\Users\Think\eclipse-workspace\jmh1>java -jar target\benchmarks.jar  org.littlestar.jmh1.Benchmark1 -prof stack  -f 1 -i 1 -r 1 -wi 1 -w 1
...
// 在执行完一次fork后,会打印profile分析报告。
Secondary result "org.littlestar.jmh1.Benchmark1.stringAppend1:·stack":
Stack profiler:

....[Thread state distributions]....................................................................
100.0%         RUNNABLE

....[Thread state: RUNNABLE]........................................................................
 81.3%  81.3% org.openjdk.jmh.infra.Blackhole.consume
 17.2%  17.2% org.littlestar.jmh1.jmh_generated.Benchmark1_stringAppend1_jmhTest.stringAppend1_thrpt_jmhStub
  1.6%   1.6% sun.reflect.NativeMethodAccessorImpl.invoke0

也可以通过Options类来指定, JMH的profiler在org.openjdk.jmh.profile包下,感兴趣的也可以自己研究源代码。

public class Test2 {
	public static void main(String[] args) throws RunnerException {
	       Options opt = new OptionsBuilder()
	    		   .include(Benchmark1.class.getSimpleName())
	    		   .forks(1)
	    		   .threads(1)
	    		   .addProfiler(GCProfiler.class) //指定执行GC Profiler,分析GC的细节。
	    		   .warmupIterations(1)
	    		   .warmupTime(TimeValue.milliseconds(1500))
	    		   .measurementIterations(2)
	    		   .measurementTime(TimeValue.milliseconds(1500))
	    		   .build();
	       new Runner(opt).run();
	}
}
/**
C:\Users\Think\eclipse-workspace\jmh1>mvn clean install
C:\Users\Think\eclipse-workspace\jmh1>java -cp target\* org.littlestar.jmh1.Test2
...
// 每次fork都会答应GC相关的分析报告。
# Run progress: 0.00% complete, ETA 00:00:13
# Fork: 1 of 1
# Warmup Iteration   1: 268842.403 ops/s
Iteration   1: 305860.471 ops/s
                 ·gc.alloc.rate:                   2590.627 MB/sec
                 ·gc.alloc.rate.norm:              11904.001 B/op
                 ·gc.churn.PS_Eden_Space:          2911.375 MB/sec
                 ·gc.churn.PS_Eden_Space.norm:     13377.846 B/op
                 ·gc.churn.PS_Survivor_Space:      0.108 MB/sec
                 ·gc.churn.PS_Survivor_Space.norm: 0.496 B/op
                 ·gc.count:                        5.000 counts
                 ·gc.time:                         5.000 ms

Iteration   2: 314701.664 ops/s
                 ·gc.alloc.rate:                   2670.340 MB/sec
                 ·gc.alloc.rate.norm:              11904.001 B/op
                 ·gc.churn.PS_Eden_Space:          2715.778 MB/sec
                 ·gc.churn.PS_Eden_Space.norm:     12106.560 B/op
                 ·gc.churn.PS_Survivor_Space:      0.093 MB/sec
                 ·gc.churn.PS_Survivor_Space.norm: 0.413 B/op
                 ·gc.count:                        6.000 counts
                 ·gc.time:                         5.000 ms



Result "org.littlestar.jmh1.Benchmark1.stringAppend1":
  310281.067 ops/s

Secondary result "org.littlestar.jmh1.Benchmark1.stringAppend1:·gc.alloc.rate":
  2630.483 MB/sec

Secondary result "org.littlestar.jmh1.Benchmark1.stringAppend1:·gc.alloc.rate.norm":
  11904.001 B/op

Secondary result "org.littlestar.jmh1.Benchmark1.stringAppend1:·gc.churn.PS_Eden_Space":
  2813.576 MB/sec

Secondary result "org.littlestar.jmh1.Benchmark1.stringAppend1:·gc.churn.PS_Eden_Space.norm":
  12742.203 B/op

Secondary result "org.littlestar.jmh1.Benchmark1.stringAppend1:·gc.churn.PS_Survivor_Space":
  0.100 MB/sec

Secondary result "org.littlestar.jmh1.Benchmark1.stringAppend1:·gc.churn.PS_Survivor_Space.norm":
  0.455 B/op

Secondary result "org.littlestar.jmh1.Benchmark1.stringAppend1:·gc.count":
  5.500 counts

Secondary result "org.littlestar.jmh1.Benchmark1.stringAppend1:·gc.time":
  5.000 ms
*/

总结

JMH提供了非常多在基准测试方面有用的特性,对于程序代码有追求的你值得拥有。JMH自带丰富的案例代码供你参考(https://github.com/openjdk/jmh/tree/master/jmh-samples/src/main/java/org/openjdk/jmh/samples), 有兴趣的去看看。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值