Fundamentals 14 min read

Using JMH for Java Microbenchmarking: A Comprehensive Guide

This article introduces JMH, explains how to add dependencies, write and run microbenchmarks for string concatenation, describes key annotations, highlights common pitfalls, and shows how to package, visualize, and integrate JMH benchmarks within Java projects.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Using JMH for Java Microbenchmarking: A Comprehensive Guide

JMH Introduction

JMH (Java Microbenchmark Harness) is a tool suite for micro‑benchmarking Java code at the method level with nanosecond precision, created by experts who understand JIT and JVM effects on benchmarks.

It is useful when you need to quantify the performance of hotspot methods and compare different implementations.

Adding Dependencies

For JDK 9 and later JMH is bundled; for earlier JDKs add the following Maven dependencies (latest version 1.23):

<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>
</dependency>

Writing a Benchmark

Create a JMH test class to compare string concatenation using the + operator versus StringBuilder.append() :

@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 5)
@Threads(4)
@Fork(1)
@State(value = Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class StringConnectTest {

    @Param(value = {"10", "50", "100"})
    private int length;

    @Benchmark
    public void testStringAdd(Blackhole blackhole) {
        String a = "";
        for (int i = 0; i < length; i++) {
            a += i;
        }
        blackhole.consume(a);
    }

    @Benchmark
    public void testStringBuilderAdd(Blackhole blackhole) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(i);
        }
        blackhole.consume(sb.toString());
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(StringConnectTest.class.getSimpleName())
                .result("result.json")
                .resultFormat(ResultFormatType.JSON).build();
        new Runner(opt).run();
    }
}

The @Benchmark annotation marks methods to be measured; the Blackhole prevents dead‑code elimination.

Running the Benchmark

Execute the benchmark via the generated Runner or package it into an executable JAR using the Maven Shade 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>

Then run:

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

JMH Core Annotations

@BenchmarkMode

Specifies the measurement mode, e.g., @BenchmarkMode({Mode.SampleTime, Mode.AverageTime}) or Mode.All .

@State

Defines the scope of shared objects: Scope.Benchmark , Scope.Group , or Scope.Thread .

@OutputTimeUnit

Sets the time unit for results (e.g., TimeUnit.NANOSECONDS ).

@Warmup

Configures warm‑up iterations, time, and batch size to let the JVM JIT optimise the code before measurement.

@Measurement

Configures the actual measurement iterations; parameters are similar to @Warmup .

@Threads

Specifies how many threads run the benchmark concurrently.

@Fork

Controls how many separate JVM processes are launched for isolation.

@Param

Provides a set of values for a field, enabling parameterised benchmarks.

Common Pitfalls

Avoid dead‑code elimination by using Blackhole.consume() or returning the result; beware of constant folding, inlining, false sharing, and other JVM optimisations that can skew results.

JMH Plugins and IDE Integration

IntelliJ IDEA offers a JMH plugin that can generate benchmark methods, run individual benchmarks, or execute all annotated methods directly from the IDE.

Visualization

Exported JSON results can be visualised with tools such as JMH Visual Chart or JMH Visualizer .

Conclusion

JMH provides a reliable way to benchmark Java code while mitigating JIT and JVM optimisation effects; by annotating methods with @Benchmark and configuring the harness appropriately, developers can obtain accurate performance metrics for their business logic.

JavaJVMperformance testingbenchmarkingMicrobenchmarkJMH
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.