Using JMH for Precise Java Microbenchmarking: Comparing System.currentTimeMillis() and System.nanoTime()
This article explains how to quickly benchmark Java code performance, introduces the JMH microbenchmarking tool, provides sample JMH benchmark code comparing System.currentTimeMillis() and System.nanoTime(), and presents the results showing nanoTime's superior throughput.
When you write Java code and need to know its performance, or when you have multiple implementation options and must choose the faster one, a quick performance test is essential.
Initially the author used a simple manual test that measured elapsed time with System.currentTimeMillis() around a loop calling doSomething() :
public static void main(String[] args) {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
doSomething();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}Later the test was wrapped in a custom concurrent utility:
public static void main(String[] args) {
def test = {
doSomething()
}
new FunQpsConcurrent(test, "Demo").start();
}Recently the author discovered JMH (Java Microbenchmark Harness), a suite designed for method‑level benchmarking with nanosecond precision, created by the engineers who implement the JVM JIT. The tool is easy to start, and the author recommends spending about two hours learning it, while also checking the official GitHub samples for common pitfalls.
For IntelliJ users, a dedicated plugin simplifies JMH usage, and visualisation tools can generate graphical reports.
The author then built a JMH benchmark to compare System.currentTimeMillis() and System.nanoTime() :
package com.funtest.groovytest;
import com.funtester.frame.SourceCode;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 2, time = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS)
@Threads(2)
@Fork(1)
@State(value = Scope.Thread)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class JmhT extends SourceCode {
@Param(value = {"10", "20", "50"})
private int length;
@Benchmark
public void mill() {
System.currentTimeMillis();
}
@Benchmark
public void nano() {
System.nanoTime();
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(JmhT.class.getSimpleName())
.result("result.json")
.resultFormat(ResultFormatType.JSON)
.forks(1)
.threads(20)
.warmupIterations(2)
.warmupBatchSize(1)
.measurementIterations(2)
.measurementBatchSize(2)
.build();
new Runner(options).run();
}
}The benchmark runs with different length parameters (which are not actually used) to demonstrate JMH’s parameterisation feature. The generated JSON result can be visualised, but the raw numbers already reveal the performance difference.
Benchmark output (text version):
Benchmark (length) Mode Cnt Score Error Units
JmhT.mill 10 thrpt 2 0.015 ops/ns
JmhT.mill 20 thrpt 2 0.015 ops/ns
JmhT.mill 50 thrpt 2 0.016 ops/ns
JmhT.nano 10 thrpt 2 0.091 ops/ns
JmhT.nano 20 thrpt 2 0.088 ops/ns
JmhT.nano 50 thrpt 2 0.084 ops/nsFrom these results, System.nanoTime() shows a much higher throughput than System.currentTimeMillis() , which may be surprising to some developers. The author notes that the visual report can be generated from the JSON file, and that IntelliJ’s JMH plugin can also produce the comparison without writing the full benchmark code.
FunTester
10k followers, 1k articles | completely useless
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.