What is AOT?

Ahead-of-Time (AOT) compilation in Java is a crucial technique that translates Java bytecode into native machine code before the application is executed. In traditional Java execution, source code is compiled into bytecode that the Java Virtual Machine (JVM) loads and runs. By performing the translation to native code beforehand, AOT allows direct execution by the operating system, which eliminates the initial need for the JVM to interpret bytecode or wait for standard compilation paths during runtime. This proactive process significantly optimizes resource utilization right from the start.

AOT Caching

While standard AOT compilation translates everything into native binary executables, AOT caching introduces a slightly different, highly effective terminology. Instead of strictly replacing code execution mechanics, the AOT compiler pre-computes and stores vital runtime artifacts directly on the disk. These runtime artifacts consist of critical class-level data, including information about fields, methods, linking profiles, and method profiling details. By making this preprocessed metadata available before runtime, the JVM can immediately reuse it upon startup, avoiding repetitive discovery and verification work. This feature, added as an experimental functionality in recent Java releases, serves as a powerful optimization tool to minimize cold-start performance penalties.

AOT compilation

Image - 1: AOT Compilation process

AOT Metadata (Project Leyden & Application Class Data Sharing)

Under the hood of OpenJDK, the JVM compiles this data using a “training run” into an AOT cache file (like a .aot or .jsa archive). JVM metadata includes: 

  • Static Metadata: Structural blueprints of your classes, fields, methods, and hierarchy. Because it is saved in the exact same binary format the JVM uses internally, the JVM can memory-map it directly into RAM without parsing raw bytecode. 
  • Constant Pool Resolutions: Pre-resolved references to strings, numbers, and methods. 
  • Method Profiles: Observations of how the application runs during warm-up. This helps the JIT compiler skip long analysis phases later on. 
  • Primordial Heap Objects: Predictable static objects (like hardcoded strings or system properties) recorded at build time and cached so they do not need to be instantiated from scratch.

How to enable AOT caching:

To use this capability, you must first initiate a training or preprocessing step to generate the cache file. The following command will execute your application, analyze the class data, and store the resulting precomputed metadata into an app.aot file on your disk:

java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.Main

Once the cache file is created, you can leverage it during subsequent production runs. This command instructs the JVM to read the pre-existing metadata from the disk, allowing the application to achieve a much faster warm-up and better overall startup performance:

java -XX:AOTCache=app.aot -cp app.jar com.example.Main

AOT

So, what is JIT?

Just-in-Time (JIT) compilation works dynamically during runtime, creating a distinct contrast with AOT techniques. The JVM always begins execution by loading bytecode via the Class Loader and interpreting it line by line. As the application operates, the JVM continuously profiles code execution to identify “hot paths”—methods and routines that are executed frequently. When a method passes a certain usage threshold, the dynamic JIT compiler compiles that specific bytecode into native machine code, optimizing it heavily based on the actual runtime behavior observed during the live session.

This dynamic optimization pipeline means that JIT compilation is mandatory and happens continuously throughout the lifecycle of standard Java processes. While it produces highly optimized code matched to real-world usage, the continuous profiling, interpreting, and ongoing compiling add considerable CPU overhead and warm-up latency at startup. This highlights the fundamental operational difference between the two systems: AOT will not happen automatically and requires developers to explicitly initiate it before runtime, whereas JIT handles optimization on the fly during execution, though both ultimately consume Java bytecode as their primary input.

Furthermore, the physical storage locations of their resulting code diverge completely. Native code produced dynamically by the JIT compiler is stored directly in the temporary runtime memory allocated to the JVM code cache, often associated with internal execution areas like MetaSpace. On the other hand, the AOT cache resides safely as a static file on the disk, persisting across application restarts so the JVM can rapidly load it whenever a new instance spins up.

JIT

Image - 3: JIT Dynamic Optimization process

Comparing AOT and JIT Compilation

The fundamental distinction between Ahead-of-Time (AOT) and Just-in-Time (JIT) compilation lies in when the translation to native machine code occurs and how that timing affects application behavior. AOT compilation completes this translation entirely before the program starts, allowing the operating system to execute native binaries immediately. This proactive approach is the primary driver for improved startup performance and a reduced memory footprint, as the JVM does not need to perform heavy lifting during the initial launch phase. Consequently, AOT is best applied in environments where rapid scaling and predictable resource usage are paramount, such as cloud-native microservices and short-lived serverless functions.

In contrast, JIT compilation embraces a dynamic, adaptive strategy that prioritizes long-term peak performance over initial speed. By compiling bytecode during runtime, the JIT compiler can observe actual data patterns and execution paths, performing sophisticated optimizations that are impossible to predict before the application is live. While this creates a “warm-up” period characterized by higher CPU overhead and latency, the resulting code is often more efficient for long-running server applications that benefit from continuous, real-world profiling. Ultimately, the choice between the two involves a trade-off: AOT provides immediate readiness and stability, while JIT offers a more tailored and high-performance execution environment as the application matures.

Difference Summary Table

FeatureAhead-of-Time (AOT)Just-in-Time (JIT)
Compilation TimeBefore application executionDuring application execution
Startup SpeedFast (Instant, code is ready).Slow (Heavy CPU overhead at launch).
Memory UsageLow (No compiler or source code in RAM).High (Requires runtime compiler in RAM).
Binary File SizeLarge (Contains pre-compiled native code).Small (Contains compact bytecode).
PortabilityLow (Tied to specific CPU architecture).High (Runs anywhere with a runtime).
Code SecurityHigh (Harder to reverse engineer).Low (Bytecode is easy to decompile).

AOT Support in modern Java Platforms

AOT supports in the latest Java versions from Java 22 onwards. This means that any Java distribution greater than 22 will support the AOT functionalities. In the case of Java 21 and 22, the AOT support is achieved by using GraalVM binaries. Later Java 25 LTS fully supports this functionality without downloading any GraalVM binaries. 

Summary

This document explains the two primary compilation strategies in Java—Ahead-of-Time (AOT) and Just-in-Time (JIT)—comparing their operational mechanisms, trade-offs, and ideal use cases.

  • Ahead-of-Time (AOT) Compilation: Translates Java bytecode into native machine code before execution, eliminating the need for runtime interpretation. This proactive approach significantly reduces cold-start latency and memory footprint, making it ideal for cloud-native microservices and short-lived serverless functions.\
  • AOT Caching: A specialized optimization technique that pre-computes and stores critical runtime artifacts (class data, method profiles) on the disk. This allows the JVM to bypass repetitive discovery tasks at startup, further minimizing performance penalties.\
  • Just-in-Time (JIT) Compilation: Operates dynamically during runtime by profiling “hot paths” and optimizing frequently executed code. While it introduces initial CPU overhead and “warm-up” latency, it excels in peak performance for long-running server applications.\
  • Key Distinctions:
    • Startup Speed: AOT is fast; JIT is slower due to initial compilation overhead.\
    • Storage: AOT results in static files on disk; JIT code resides in the JVM’s runtime memory (code cache).\
    • Portability: JIT offers high portability; AOT is often tied to specific CPU architectures.\
    • Security: AOT code is harder to reverse engineer than JIT-compiled bytecode.\
  • Platform Support: AOT support is available in Java distributions from version 22 onwards. Java 21 and 22 rely on GraalVM binaries for this functionality, while Java 25 LTS provides full, native support.

\