Microbenchmarking Java code with JMH
Tags: benchmark, java, gradle, jmh
Published: 2021-09-23 23:56:46

---
title: Microbenchmarking Java code with JMH
published: true
date: 2021-09-23 00:00:00 UTC
tags: Benchmark,Java,Gradle,JMH
canonical_url: https://MihaiBojin.com/application-settings/micro-benchmarking-java-code-with-jmh
cover_image: https://unsplash.com/photos/ij5_qCBpIVY/download?force=true&w=1920
series: Designing a library for reading application settings in Java
---

🔔 This article was originally posted on my site, [MihaiBojin.com](https://MihaiBojin.com/application-settings/micro-benchmarking-java-code-with-jmh?utm_source=DevTo&utm_medium=organic&utm_campaign=top-promo "MihaiBojin.com"). 🔔

* * *

As a general best practice, it's a good idea to benchmark your code.

[JMH](https://github.com/openjdk/jmh), or Java Microbenchmark Harness, is a tool that can be used to analyze the performance of JVM languages.

Since I wanted to profile the performance of the [Props](https://github.com/props-sh/props) library, I integrated _JMH_ into the codebase.

The following is a simple step-by-step tutorial about integrating the JMH Gradle plugin in a Java codebase.

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#gradle-configuration)Gradle configuration

First, add the [JMH Gradle plugin](https://github.com/melix/jmh-gradle-plugin) in your `build.gradle.kts` file:

```
plugins {
    id("me.champeau.jmh").version("0.6.6")
}
```

Doing so will add a few tasks to your Gradle project:

- `gradle jmh`: Runs all benchmarks
- `gradle jmhJar`: Generates a portable JAR that you can run on a different machine

The second target is helpful for running the benchmarks on a dedicated machine (that is not your developer laptop), resulting in predictable, comparable, and reproducible results.

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#writing-a-jmh-benchmark)Writing a JMH benchmark

The plugin expects all the benchmark code to exist in `src/jmh/java` and `src/jmh/resources`. This avoids having to create a separate project and importing all the code while at the same time avoiding shipping the benchmark code with the main library in `src/main/java`.

Let's create the first benchmark. Save this file as `src/jmh/java/Benchmark.java` in your module.

```
@Fork(value = 1, warmups = 1)
@Warmup(iterations = 1)
@Measurement(iterations = 1)
@OutputTimeUnit(TimeUnit.SECONDS)
public class Benchmark {
  @Benchmark
  public static void oneBenchmark() {
    // do something
  }
}
```

The code above is just scaffolding. Let's look at each annotation.

_@Fork_: configures how many times the current benchmark is forked. If `value=0`, the benchmark will be run in the same JVM. The `warmups` parameter defines how many times the benchmark is forked (but the results discarded).

The main benefit of warming up is to load all classes and cache them. Unfortunately, since the JVM uses lazy loading and Just In Time compiling, the first iteration of our benchmark would incur the cost of all these actions and skew the results.

_@Warmup_ determines how many warmups are performed and discarded per fork.

_@Measurement_ allows us to specify how many iterations to execute per benchmark.

And finally, _@OutputTimeUnit_ allows us to specify the unit reported in the results.

There are more annotations and parameters, but I won't get into the weeds of it just yet.

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#consuming-results)"Consuming" results

There is a small caveat when writing benchmarking code in that the JVM is smart enough to optimize code that is not actually used.

For example, in the following code, the result of `tested.get()` is never used (consumed) so the running JVM may decide to simply skip the call altogether, making the benchmark invalid.

```
public class Benchmark {
  @Benchmark
  public static void oneBenchmark() {
    // assume an object under test
    tested.get();
  }
}
```

JMH introduces the concept of a `Blackhole`. The code above can be rewritten, ensuring the results are always used and the code being benchmarked is executed:

```
public class Benchmark {
  @Benchmark
  public static void oneBenchmark(Blackhole blackhole) {f
    // the code under test is always executed
    blackhole.consume(tested.get());
  }
}
```

You can now run all the benchmarks with the `gradle jmh` command.

Et voila! A super simple intro to JMH in a Gradle project!

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#further-reading)Further reading

For additional details, see:

- a slightly more in-depth [JMH tutorial](https://www.baeldung.com/java-microbenchmark-harness)
- a description of [JVM warmup](https://www.baeldung.com/java-jvm-warmup)
- extra configuration options for the [JMH Gradle plugin](https://github.com/melix/jmh-gradle-plugin#configuration-options)
- the [JMH project](https://github.com/openjdk/jmh)

Until next time!

* * *

If you liked this article and want to read more like it, [please subscribe to my newsletter](https://motivated-founder-807.ck.page/db1cf284bf "newsletter link"); I send one out every few weeks!