Skip to main content
๐Ÿ“œ WAYPOINT LESSON

nanoTime, warmup, and the JIT threshold

โญโญโญ advancedโณ 16 min read๐Ÿ“ Lesson 153 of 180

Monotonic timing, asynchronous JIT, why first runs lie, and the measure-warm-code rule.

System.nanoTime() is monotonic: it never goes backward, is unaffected by NTP or user clock changes, and is only meaningful as a DIFFERENCE between two reads. System.currentTimeMillis() is a wall clock โ€” it can jump. Timing arithmetic uses nanoTime; date/time display uses the wall clock. Confusing them is how you time a test during a DST changeover.

long t0 = System.nanoTime();
work();
long nanos = System.nanoTime() - t0;   // duration; divide for ms

JIT compilation is asynchronous and happens per-method, on thresholds: the interpreter counts invocations/back-edges, and at roughly 10k the method is queued for C1 (fast compile) and later C2 (full optimization). Timing an unwarmed method measures the interpreter plus the compiler's attention, not your code. Hence the golden rule: measure warm code โ€” run the workload some thousands of times first, discard that data, then measure. fork() between benchmark JVMs and the absence of dead-code elimination are why JMH (@Benchmark) exists at all: an ad-hoc timer is a benchmarking toy.