Articles

AOT vs JIT: Understanding the Java Compiling Processes.

7 min read

AOT vs JIT: Understanding the Java Compiling Processes

In the world of Java development, performance optimization is crucial for delivering efficient and responsive applications. A significant factor influencing performance is the compilation method used by the Java Virtual Machine (JVM). This blog post will explore two primary compilation techniques: Just-in-Time (JIT) compilation and Ahead-of-Time (AOT) compilation. By understanding these methods, developers can make informed decisions to optimize their Java applications.

What is a Compiler and What Does It Do?

A compiler is a specialized program that translates high-level programming language code into a lower-level language, typically machine code. This machine code can be executed directly by a computer's processor. The primary purpose of a compiler is to transform human-readable code into a format that a machine can efficiently execute, ensuring the resulting executable program runs as intended.

Compilers perform several key tasks:

  1. Lexical Analysis: Breaking down the source code into tokens.

  2. Syntax Analysis: Parsing tokens to ensure they follow the language's grammatical rules.

  3. Semantic Analysis: Ensuring that the syntax is meaningful and consistent with the language's semantics.

  4. Optimization: Improving the code to run more efficiently.

  5. Code Generation: Translating the optimized code into machine code.

  6. Code Linking: Combining different code modules into a single executable.

The Overview of the Java Compilation Process

The Java Development Kit (JDK) compiler, known as ‘javac’, is an integral part of the Java programming environment. It transforms Java source code into bytecode, an intermediate language that the Java Virtual Machine (JVM) can execute. This process is foundational to understanding the roles of Just-In-Time (JIT) and Ahead-Of-Time (AOT) compilation.

Here's a concise overview of how javac works:

  • Parsing: The compiler reads the Java source code (.java files). Break it down according to the Java language syntax and check for syntax errors. This process generates a set of syntax trees.

  • Type Checking: The compiler examines the syntax trees for type errors. Ensuring that methods are called with the correct number and types of arguments and that variables are used consistently with their declared types.

  • Bytecode Generation: If no errors are found, the compiler converts the syntax trees into Java bytecode instructions. These instructions are stored in .class files, one for each class defined in the source code.

  • Linking: At runtime, the JVM links the classes together. Ensure that all referenced classes can be found and verify that they contain the methods and fields the program expects.

The bytecode generated by ‘javac’ is platform-independent, allowing it to be executed on any device with a JVM. This platform independence is central to Java's "write once, run anywhere" philosophy. The JVM then interprets or compiles the bytecode into machine code at runtime, enabling execution by the computer's processor. This sets the stage for the JVM's JIT or AOT compilation methods, which further optimize the code for performance.

By converting source code into bytecode and leveraging the JVM's capabilities, Java ensures both portability and efficiency in software delivery. This dual-stage compilation process, combined with either JIT or AOT, plays a significant role in achieving optimal performance in various computing environments.

Just-in-Time Compilation

Just-In-Time (JIT) compilation is a mechanism employed by the Java Virtual Machine (JVM) to enhance the runtime performance of Java applications. Here's an overview of how JIT compilation operates:

  • Bytecode Interpretation: When a Java application starts, the JVM initially interprets the bytecode. This bytecode is a platform-independent intermediate representation derived from Java source code. The JVM reads and executes this bytecode line by line, converting it into machine code that the computer’s processor can understand. Machine code, or machine language, is the fundamental language of computers, composed of binary numbers that the CPU interprets as ones and zeros. This translation from human-readable source code to binary machine code is necessary because computer hardware can only process instructions in binary form.

  • HotSpot Identification: While interpreting the bytecode, the JVM monitors the execution to identify sections of the code that are executed frequently, known as "hot spots".

  • JIT Compilation: When the JVM detects a hot spot, the JIT compiler is activated. The JIT compiler translates the hot spot from bytecode into native machine code, which can be executed directly by the computer’s processor. This native code is then stored for subsequent use.

  • Direct Execution: On encountering the hot spot again, the JVM bypasses the interpretation phase and directly executes the precompiled machine code. This phase, often referred to as "warm-up," is the period during which the Java application transitions to optimal performance by utilizing the compiled machine code. The JIT compiler's role is to ensure that this compiled code is highly optimized, significantly boosting the application’s performance.

The JIT compiler is integrated into the JVM. Popular JVM implementations, such as Oracle’s HotSpot and OpenJDK, include JIT compilation capabilities. The JIT compiler is specifically designed to maximize the execution speed of Java applications, making Java a strong contender for high-performance computing tasks.

Ahead-of-Time Compilation

Ahead-Of-Time (AOT) compilation is a technique that transforms Java bytecode into native machine code before the application is executed. This differs from the traditional Just-In-Time (JIT) compilation, which converts bytecode to machine code at runtime.

Here is how AOT compilation works in Java:

  • Compilation: Using an AOT compiler, such as the jaotc tool introduced in JDK 9, the Java bytecode is compiled into native machine code ahead of the application's execution. This compilation process occurs separately from the application's execution, ensuring that the native code is ready before the application starts.

  • Linking: The compiled native machine code is then linked with the JVM. This linking step ensures that the native code integrates seamlessly with the JVM, allowing for proper interaction during execution.

  • Execution: When the application is launched, the JVM can immediately execute the pre-compiled native code. This eliminates the need for the JVM to interpret or compile bytecode to machine code at runtime, resulting in faster startup times and reduced runtime overhead.

AOT compilation is especially advantageous for applications that require quick startup times or operate in environments where runtime performance is critical. While AOT compilation can significantly enhance performance, it is not intended to completely replace JIT compilation. Instead, it works alongside JIT compilation. The JVM can still employ JIT compilation for code sections that were not pre-compiled using AOT.

GraalVM is a notable JVM that supports AOT compilation. It features a tool called native-image, which can generate standalone native executables from Java applications. These executables package the application, necessary libraries, JDK, and a streamlined JVM, allowing them to run independently without a separate JVM installation.

AOT vs JIT

Comparing AOT and JIT involves weighing the trade-offs between compile-time and runtime performance. It also includes evaluating memory usage and optimizing based on actual execution.

Some platforms use AOT compilation to increase performance and reduce page load time. Examples include Google Search, Forbes, PayPal, and Upwork. Other websites and applications use JIT compilation to enhance interactivity and ensure smooth operation. Examples include Google Maps, Gmail, Slack, and Trello.

Conclusion

Both AOT and JIT compilation have their strengths and suit different applications. AOT is beneficial for applications where startup time and memory usage are critical, and the execution environment is relatively static. JIT excels in environments where runtime optimization can yield significant performance gains and the workload varies dynamically.

Understanding the specific requirements of your application and its environment is essential to choosing the right compilation strategy. By leveraging the strengths of both AOT and JIT, developers can maximize performance, reduce latency, and ensure efficient resource usage.

In conclusion, the debate between AOT and JIT is about finding the most appropriate choice for a given context, not about which is superior. By making informed decisions based on your application's unique needs, you can harness the full potential of these powerful compilation techniques.

Find out what one connected Zoho system would change in the way you run.

In a no-risk discovery we look at your CRM, finance and operations systems and who owns each part, and show what a connected system would do differently. You pay only if you proceed. Or see how we approach it.

Book the no-risk discovery

More on the same problem:

Frequently asked questions.

When should you use AOT compilation in Java?

Use ahead-of-time compilation when startup time and memory footprint matter more than peak throughput: command-line tools, serverless functions, microservices that scale up and down often, and containers where many small instances run. Compiling to a native executable removes the interpreter and warm-up phase, so the application responds immediately. The cost is less runtime optimization, longer build times and some restrictions on reflection and dynamic class loading, so applications that rely heavily on those features need extra configuration.

Why is JIT compilation the default in Java?

Because it gives the best steady-state performance for long-running applications. The JVM starts by interpreting bytecode, watches which methods run most often, and compiles those hot spots to native code using information only available at run time: which branches are taken, which types actually appear, which calls can be inlined. Those optimizations are often better than what a compiler can do in advance. For servers that run for hours or days, the warm-up cost is paid once and the gains last.

What is JIT warm-up and how do you reduce it?

Warm-up is the period after a Java application starts when it is still interpreting bytecode and has not yet compiled its hot paths, so it runs slower than it eventually will. It can last seconds to minutes depending on the workload. Ways to reduce it include tiered compilation, which compiles quickly first and optimizes later, class data sharing to cut class loading time, warming the application with representative traffic before it serves users, and ahead-of-time compilation of the code the JVM would otherwise compile at startup.

Does AOT compilation make Java faster?

It makes Java start faster and use less memory, which is a different thing from running faster. A native executable runs at full speed from the first request, with no interpreter and no warm-up. A JIT-compiled application, once warm, usually runs hotter code faster because it optimized with run-time information. So AOT wins on time to first response and footprint, JIT wins on peak throughput over a long run, and the right choice depends on how long the process lives and how it is deployed.

Can AOT and JIT compilation be used together in Java?

Yes. The JVM can load ahead-of-time compiled code for the classes it was given and still JIT-compile anything that was not precompiled, which was the design of the early JDK AOT tooling. Some modern approaches also profile an application in advance and record which methods to compile at startup, combining fast start with run-time optimization. A fully native image is the opposite end, where everything is compiled ahead and there is no JIT at run time.

Where we're not the right answer

We'll tell you if we're a fit. If we're not, we'll tell you that too.

  • Your current stack works and nobody wants to change it
  • You want licences resold at a discount and nothing else
  • Your internal team owns the operating model and is not handing it over
  • You want hours of configuration work and nothing run for you: that is on our services pages, and it is not a managed solution
How we start

Most of our best clients come to us with a feeling, not a plan.

"Something isn't working." "We're outgrowing our tools." "We're afraid to make the wrong move." No-Risk Discovery is a short, practical conversation that gets you clarity before you commit to anything big. We'll tell you if we're a fit. If we're not, we'll tell you that too.

Talk to us

Have a question about this?

Say who you are and ask. A person replies within one business day.

The rest of the form follows your answer.

A new client, or thinking about becoming one

Never shared. Never sold.

A solution, a service, or not sure yet.

Platforms you run today

Pick every one that applies. Choosing one is an answer too.