OpenJDK's JEP 544 cuts Java startup time up to 80% with AOT caching

JEP 544, part of OpenJDK's Project Leyden initiative, is a proposal to extend the HotSpot Java Virtual Machine's existing ahead-of-time (AOT) cache so that it also stores fully optimized native machine code, not just the loaded classes and profiling data it already holds. The idea is to compile an application in a dedicated training run, save the resulting native code (called AOT code) in the cache, and then let a production run of the same application load that code instantly at startup instead of interpreting bytecode and recompiling it through the just-in-time (JIT) compiler from scratch. If the application's workload changes once it is in production, HotSpot still falls back to its normal dynamic behavior: it deoptimizes the stale code and generates fresh native code for whatever methods have become hot, exactly as it does today.

The JEP frames the problem around how HotSpot normally runs a Java program: it starts up by interpreting bytecode while profiling which methods are called often, their hot spots; it then warms up by using the basic C1 compiler to turn those hot methods into modestly optimized native code, and later the advanced C2 compiler to recompile the hottest of them into fully optimized code once enough profile data has accumulated; only then does it reach peak performance. That profiling and compiling work costs real CPU time and memory, which is why Java applications are historically slow to reach full speed, a particular pain point for short-lived processes such as microservices. A purely static, ahead-of-time compiler avoids the warmup penalty entirely but gives up three things dynamic compilation provides: the ability to reoptimize when a workload's hot spots shift, portability across different hardware and JDK versions without recompiling, and compatibility with Java features that only resolve at run time, such as dynamic class loading and reflection. JEP 544's premise is that caching real AOT-compiled code, while keeping HotSpot's JIT and deoptimization machinery active as a fallback, can deliver the fast start of static compilation without giving up those three advantages of the dynamic approach.

JEP 544 is presented as the third step of a sequence. JEP 483, delivered in JDK 24, first shifted class loading and linking into the training run, caching the loaded and linked forms of classes to speed up startup. JEP 515, delivered in JDK 25, added cached execution profiles from the training run so the C2 compiler can start working immediately in production instead of waiting to collect fresh profile data. JEP 544 is the step that adds the actual compiled native code itself to that same cache.

No changes to application, library or framework code are required, and using the feature needs no extra configuration beyond the existing two-step AOT workflow: a training run is launched with a flag such as -XX:AOTCacheOutput=app.aot to produce the cache file, and a later production run is launched with -XX:AOTCache=app.aot to consume it. Once a cache is being generated, HotSpot stores AOT code in it by default; no separate switch is needed to turn that on. Because AOT code and JIT code are produced by the same two compilers, C1 and C2, the JEP describes them as fully interoperable and able to coexist within a single run: whenever AOT code for a method is missing, incompatible with the current run, or later invalidated by deoptimization, execution simply falls back to the interpreter and JIT path that HotSpot already uses. The feature is scoped to the Serial, Parallel, G1 and ZGC garbage collectors, and, for now, to the AArch64 and x64 processor architectures; JEP 544 explicitly does not aim to support every architecture HotSpot runs on, leaving that to ordinary porting work, and it does not support cross-compilation, so code trained on one CPU architecture and feature set must run in production on that same architecture and feature set.

To measure the effect on startup, the JEP's authors ran five benchmark applications built with popular Java frameworks on a two-core Linux/x64 machine, a setup chosen to emulate a microservice environment where the JIT compiler competes with the application itself for CPU time. The existing AOT cache, without AOT code, already cut those applications' startup time by roughly 50% to 70%; adding AOT code pushed that reduction to roughly 65% to 80%. A separate test measured warmup using a javac-based benchmark that repeatedly compiles the same 50 source files across 20 iterations. On the first iteration, which reflects startup, the AOT cache alone improved time by about 30%, and adding AOT code contributed a further 45%, for a combined improvement of about 75%; on the iterations after that, which reflect warmup, the run using AOT code was already close to its fully warmed-up, steady-state performance by the fourth iteration, while the run without it took longer to converge to roughly the same eventual steady state.

The version of the JEP text retrieved for this story cuts off mid-sentence partway into a later section comparing AOT and JIT code in more detail, so any material past that point, along with the usual JEP metadata sidebar (status, owner, target release, discussion list), is not covered here; no individual author is named in the retrieved text, which is written throughout in the first person plural as OpenJDK's own account of the proposal.

Key facts

  • JEP 544, part of OpenJDK's Project Leyden, extends the HotSpot JVM's existing ahead-of-time (AOT) cache to also store fully optimized native code compiled during a training run, for instant use when a production run starts.
  • On five benchmark applications running on a two-core Linux/x64 system, the AOT cache without AOT code cut startup time by about 50% to 70%; adding AOT code raised that to about 65% to 80%.
  • In a javac benchmark that recompiles the same 50 source files across 20 iterations, the cache alone cut first-iteration time by about 30%; adding AOT code brought a further improvement of about 45%, for a total of about 75%, and that run was already close to its steady-state warmup performance by the fourth iteration.
  • JEP 544 builds on two earlier JEPs from the same effort: JEP 483 (JDK 24), which cached loaded and linked classes, and JEP 515 (JDK 25), which cached profiling data for the C2 compiler.
  • No application, library or framework code changes are required; HotSpot falls back to its normal interpreter and JIT path whenever AOT code is missing, incompatible or deoptimized, and the feature currently covers the x64 and AArch64 architectures with the Serial, Parallel, G1 and ZGC garbage collectors.

Why it matters

Startup and warmup lag is one of the most cited weaknesses of running Java in short-lived processes: containers, functions and other microservices that start, do a small amount of work and stop before the JIT compiler has time to fully optimize anything. The traditional fix has been a straight trade-off: compile everything ahead of time and get instant peak speed but lose the ability to adapt when the workload changes, or compile just in time and keep that adaptability but pay a real startup and warmup cost on every run. JEP 544 is Project Leyden's attempt to avoid choosing: it caches the actual optimized machine code produced in a rehearsal run so a later production run can load it immediately, while keeping HotSpot's ordinary JIT and deoptimization machinery in place as a fallback if the live workload turns out to differ from the training run. It requires no changes to application, library or framework code, which is what makes it a general-purpose fix rather than a technique individual teams have to opt into line by line.

Who it affects

The people who benefit are Java and JVM developers and operators running HotSpot-based services, especially in cloud-native and microservice settings where the same kind of CPU contention the JEP's own benchmarks emulate is common: many short processes competing for limited cores, where every second spent interpreting and JIT-compiling is a second not spent on the actual workload. Framework and library authors are not required to change anything; that is an explicit goal of the JEP. The feature does not yet reach every deployment: it currently targets only the x64 and AArch64 processor architectures and the Serial, Parallel, G1 and ZGC garbage collectors, so teams on other architectures gain nothing until, in the JEP's words, normal porting activities extend support.

How to use it

The workflow is the same two-step AOT-cache process OpenJDK already ships, unchanged by this JEP: a training run is launched with a flag such as -XX:AOTCacheOutput=app.aot to produce a cache file, and the production run is launched with -XX:AOTCache=app.aot to consume it. No other options or configuration are needed; once a cache is being generated, HotSpot stores the optimized native code in it by default. Because AOT code and JIT code come from the same two compilers, C1 and C2, they can coexist in a single run, and if the cached code for a method turns out to be missing, incompatible, or is later deoptimized, execution simply falls back to the interpreter and JIT path HotSpot already uses today, so there is no separate mode to fail out of. As an OpenJDK proposal there is no license or price to weigh: it is a JDK feature, not a separate product.

How solid is it

This is a JEP working through OpenJDK's own process, essentially a detailed design document, and the retrieved text does not state a target JDK release, a status such as Candidate or Targeted, a date, or any individual author or reviewer; it is written throughout as OpenJDK's own first-person account, and the only named efforts are OpenJDK and Project Leyden. That is a contrast with the two JEPs it explicitly builds on, JEP 483 and JEP 515, both of which the text ties to specific shipped releases, JDK 24 and JDK 25. The performance numbers are the project's own benchmarks, not third-party or independently reproduced ones: five unnamed applications built with unnamed popular Java frameworks on a single two-core Linux/x64 machine, plus one javac-based recompilation test, with no figures given for the other side of the ledger, such as extra disk space for the cache or added build-pipeline time. The copy of the page used for this story also cuts off mid-sentence partway into a later section comparing AOT and JIT code in more detail, so whatever follows there, and any metadata sidebar the live page carries, is not reflected in this account.

Risks and caveats

JEP 544 is explicit about what it does not do. It is not an AOT-only mode: JIT compilation and HotSpot's deoptimize-and-reoptimize cycle stay active in every run, so this accelerates startup rather than replacing dynamic compilation. It does not support cross-compilation: code compiled in a training run must run on the same CPU architecture and feature set in production, so a training run done on one machine cannot be reused as is on different hardware. And support does not yet cover every architecture HotSpot runs on, only x64 and AArch64 for now. Because a training run's workload can differ from what actually shows up in production, the JEP itself notes that AOT-compiled code and JIT-compiled code generated for the same method are not guaranteed to be identical, which is exactly why the JIT and deoptimization fallback has to remain in place rather than being treated as a rare edge case.

“Java applications will gain some of the benefits of static compilation while retaining the agility, portability, and compatibility of dynamic compilation.”

— JEP 544