一、注意事项

1.1、避免DCE(Dead Code Elimination)

DCE 就是 JVM 在优化时为我们主从擦去上下文无关的内容,甚至是计算后不会使用的代码。
比如:

  1. int x = 1;
  2. int y = 2;
  3. int z = x * y;

如果基准测试方法内容为上述内容,则 JVM 可能认定其为空的方法,影响基准测试。

1.2、Blackhole 使用

Blackhole 能够在无法返回的时候保证
案例代码如下:JmhDemo.java
image.png

1.3、避免常量折叠(Constant Folding)

Java 早期的编译优化会造成常量折叠。
如:

  1. private int x = 1;
  2. private int y = x * 100;

如上述代码,在编译期间 y 会被直接赋值为 100 ,这就是所谓的常量折叠。

1.4、避免循环展开(Loop Unwinding)

在基准方法中要尽可能少的,甚至不适用循环,因为循环代码在运行阶段(JVM后期优化)会被重点关照,进行优化,这种优化叫做”循环展开“。
如:

  1. int sum = 0;
  2. for( int i = 0 ; i < 100 ; i++ ){
  3. sum += i;
  4. }
  5. <b>可能</b>被优化成
  6. int sum = 0;
  7. for( int i = 0 ; i < 20 ; i += 5 ){
  8. sum += i;
  9. sum += i + 1;
  10. sum += i + 2;
  11. sum += i + 3;
  12. sum += i + 4;
  13. }

1.5、Fork 使用

Fork 可以用于避免 Profile-guided optimizations。
Fork 通常设为 1 ,表示每次运行基准测试方法都会开辟新的进程运行,防止环境污染。

Profile-guided optimizations 案例

代码如下:JmhDemo.java
image.png
Inc1Inc2 实现完全一样,同时基准测试方法也一样,但是两者的基准测试可能存在差异

二、高级用法

2.1、Asymmetric Benchmark

针对某个方法同时进行读写操作,测试线程安全。

案例:针对 AtomicInteger 同时进行读写操作,验证性能。
代码如下:JmhDemo.java
image.png
执行结果如下
image.png

2.2、Interrupts Benchmark

在某些情况下,执行容器的某些操作会引起阻塞,该阻塞不是容器无法保证线程安全问题引起的,而是JMH 框架的机制引起的,比如:BlockingQueue。
BlockingQueue 在获取数据,如果是队列中没有数据会阻塞。或者在往队列汇总放数据,队列满了导致的阻塞问题。
案例代码:JmhDemo.java
image.png
在 put or take 操作,在 warmup 或者 measure 时都有可能出现阻塞的问题,一旦出现阻塞,就会导致整个基准测试陷入阻塞,阻塞状态只有等到批次iteration 超时(默认:10分钟)后才会继续进行。

解决:JMH 提供每个批次超时设置,超时的批次不会被纳入度量统计中

解决代码:JmhDemo.java
image.png

三、案例:各大Map容器 基准测试

测试MAP 容器

  • ConcurrentHashMap
  • ConcurrentSkipListMap
  • HashTable
  • Collections.synchronizedMap

测试代码JmhDemo.java
image.png
image.png

测试结果
image.png
type 1 的性能最好,type 1 对应 ConcurrentHashMap