Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java JMH benchmarks: consumed results, fixtures and limited measurements

Last updated: 29 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

JMH is a benchmarking runner that controls repeated measurements, warmup and JVM forks while keeping benchmark results observable to the runner.

Download Java source kit

Java 21+. This benchmark requires JMH 1.37 and its annotation processor; the downloadable Maven project declares them.

Choose a question the fixture can answer

This benchmark compares exact integer membership in a HashSet and an ArrayList for declared sizes and lookup keys. Setup constructs both structures outside the measured method. Returning the boolean result keeps the lookup result observable instead of measuring a discarded computation.

The parameters include a present and an absent key under the selected sizes. A missing lookup scans more of the list than an early present lookup, so one carefully chosen hit cannot establish a universal container ranking. Input distribution belongs in the report.

The source is compiled with JMH 1.37 and its annotation processor in the downloadable Maven project. The processor generates runner metadata. A plain Java main with a nanoTime loop does not provide the same runner or isolation.

Separate smoke verification from a useful experiment

The local check uses one short warmup and measurement iteration to verify the packaged runner can run. Those numbers are not published as a performance conclusion. A useful experiment needs longer measurements, more forks, representative inputs, error estimates and stated JVM and hardware settings.

Dead-code elimination, constant folding, allocation and retained state can change what is measured. The thread-scoped fixture declares per-worker ownership. If the real application shares one contended structure, that is a different benchmark and needs a different state scope and operation mix.

Working program

Java
package in.aitrove.bench;
import org.openjdk.jmh.annotations.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MembershipBenchmark {
    @Param({"64","4096"}) public int size;
    @Param({"63","8192"}) public int key;
    Set<Integer> indexed;List<Integer> scanned;
    @Setup public void prepare(){
        indexed=new HashSet<>();scanned=new ArrayList<>();
        for(int value=0;value<size;value++){indexed.add(value);scanned.add(value);}
    }
    @Benchmark public boolean indexedLookup(){return indexed.contains(key);}
    @Benchmark public boolean scannedLookup(){return scanned.contains(key);}
}

Measurement result

Output
Measured scores depend on the selected parameters, JVM, machine and measurement configuration.

Costs and boundaries

Fixture construction and retained storage are O(n) for n inserted values. A list membership scan is O(n); hash membership has expected constant lookup work under its normal assumptions. Actual nanoseconds and allocation behavior require measurement and are not supplied as universal facts here.

Common Mistakes

  • Do not use smoke-run scores as a production ranking.
  • Setup work should be included or excluded according to the question, not by accident.
  • A returned result still needs an actual JMH-generated runner to run this class.

Read next

Operation choice, Diagnostic observations.

Run the packaged runner

bash
mvn package
java -jar target/benchmarks.jar -wi 3 -i 5 -f 3
java
jmh-benchmarks
Storage details