JEP 544, owned by John Rose, extends HotSpot’s AOT cache to store optimised native code produced during a training run, so that peak-quality code is available the moment a production JVM starts. It is the third step of Project Leyden: JEP 483 moved class loading and linking earlier (JDK 24), JEP 515 moved method profiling earlier (JDK 25), and this moves compilation itself. The thesis is stated plainly — the way to improve startup and warmup is to do the work earlier rather than just in time.
How it works:
- Training run:
java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App ...— same workflow as before, but the cache now also holds AOT code for hot methods - Production:
java -XX:AOTCache=app.aot -cp app.jar com.example.App ...— no new options required - AOT code and JIT code come from the same C1/C2 compilers, so they coexist and interoperate; anything missing, unsuitable or later deoptimised falls back to the interpreter and JIT as usual
The constraints are the interesting part:
- Same CPU architecture and feature set — code built for an x64 chip with AVX-512 will not run on one without it
- Same garbage collector, because AOT code carries GC-specific read/write barriers
- Violate either and HotSpot warns, skips the AOT code, keeps the pre-linked classes and profile data, and runs correctly — just slower to warm up
AOT code also cannot be identical to JIT code by construction. Class initialisation order varies between runs, so C2 compiles two versions of methods touching another class’s statics: a slow one with initialisation checks, and a fast one used once everything is initialised. A static final value that the JIT can fold in as a constant — a timestamp, say — is unknown at AOT time and must be loaded explicitly.
The measured gains are real:
- Five framework benchmarks on a two-core Linux/x64 box, deliberately chosen so the JIT competes with the app: the cache alone cut startup roughly 50–70%, and with AOT code, roughly 65–80%
- A
javacbenchmark compiling the same 50 files twenty times: about 30% off startup from the cache alone, roughly 45% more from AOT code, and near steady state by the fourth iteration
What the JEP refuses to do is equally deliberate. There is no AOT-only mode, no cross-compilation, and AOTMode=required exists so that an unusable cache fails loudly instead of silently degrading. The argument against static compilation — that dynamic compilation keeps applications agile when hot spots move, portable across hardware and JDK versions, and compatible with reflection and dynamic loading — survives intact.
Future Work is honest about the limits: pushing AOT to near-total coverage produces caches so large that loading them costs more than interpreting, and starving the JIT lowers peak performance. The result is not a replacement for run-time compilation but a cache in front of it — startup benefits of static compilation with the JIT still there as the escape hatch.