# jmh-basics Companion code for the ankurm.com post *"JMH Tutorial: Writing Java Microbenchmarks That Don't Lie."* Module in `java-core-examples`, the Java-core series. There is deliberately no `docs/` folder: the deeper material lives in collapsible sections inside the post itself. ## Versions this was built and tested against | Component | Version | Notes | |---|---|---| | JDK | 25.0.5+8 (Temurin, LTS) | Compiler blackholes were auto-detected on this JVM; see `output/02`. | | JMH | 1.37 | Latest GA on Maven Central; released 3 Aug 2023. | | JUnit Jupiter | 5.11.0 | | | Maven | 3.9.11 | | | Host | 2 vCPU shared sandbox | Read the shape of the numbers, not the third significant digit. | ## Quickstart ```bash export JAVA_HOME=/path/to/jdk-25 mvn -q package -DskipTests # builds target/benchmarks.jar (shaded, runnable) java -cp target/classes com.ankurm.jmhbasics.NaiveTimingDemo java -jar target/benchmarks.jar DeadCodeBenchmark java -jar target/benchmarks.jar AllocationBenchmark -prof gc ``` `scripts/run-all.sh` regenerates every file in `output/` (about 8 minutes on 2 vCPUs). `scripts/run.sh ` runs one demo class ad hoc. ## What's in here | File | What it shows | |---|---| | `NaiveTimingDemo.java` | The `System.nanoTime()` loop: a discarded result reports 0.000 ns/op, and the kept version changes from round to round while the JIT warms up. | | `DeadCodeBenchmark.java` | Empty baseline, discarded result, returned result, `Blackhole`, and constant folding: `Math.pow` of a constant folds, `Math.log` of a constant does not, and a `final` instance field IS folded like a constant (an early draft of the Javadoc claimed the opposite; the measurement and JMHSample_10 both disagreed). | | `LifecycleDemo.java` | Counts how often every `@Setup`/`@TearDown` level fires, by running JMH with `forks(0)` in-process. | | `ForkingBenchmark.java` | One shared call site, two implementations, run with `-f 0` and `-f 1` to show type-profile pollution. | | `StateScopeBenchmark.java` | `Scope.Thread` vs `Scope.Benchmark` with two threads. | | `AllocationBenchmark.java` | Escape analysis: the same `new Point(..)` allocates 0 or 24 bytes per call depending on whether it escapes. Use `-prof gc`. | | `SumBenchmark.java` | `@Param` over three input sizes, `@Setup` outside the measured region. | | `SortBenchmark.java` | Warmup trace (`-wi 10 -w 200ms`) and three benchmark modes in one run. | | `JmhBasicsTest.java` | Pins the deterministic claims: lifecycle counts, generated benchmark list, benchmark bodies. No timing assertions. | | `output/01-12` | Captured runs of all of the above. | ## Notes worth knowing before reading the post - **Each `@BenchmarkMode` value is a separate benchmark entry.** `SortBenchmark.sortCopy` carries three modes, so the generated `META-INF/BenchmarkList` has three lines for it (21 entries for 19 methods; the test pins this). - **Run `LifecycleDemo` from the shaded jar**, not from `target/classes`: JMH is not on the plain classpath. - **Timing figures drift.** Several error margins in `output/` are wide on this shared host. The post draws conclusions only from differences far larger than the error bars. ## License MIT - see the [repo-wide LICENSE](../LICENSE).