Last week we looked at some of the JVM command line options that customise how the interpreter and JIT compiler work. This week we’ll look at the AOT compiler that was introduced experimentally in Java 9 (and removed in Java 16 – more on that later).
What is AOT Compilation?
To recap on some points from the previous post, the HotSpot JVM contains a JIT compiler to speed up Java applications at runtime. The HotSpot JVM profiles a running program, and then selectively optimizes sections of code it decides will have the greatest speed benefit. These hot spots are compiled into native code on the fly.
JIT compilers are fast, but when running large Java programs the JIT can take a long time to warm up completely. Rarely used methods might never even be compiled, and are then only executed by the interpreter. This can potentially cause performance hits.
Ahead of Time (AOT) compilation takes a different approach to the JIT. An AOT compiler compiles Java classes to native code before running the Java application. This is one way of improving the performance of Java applications, and in particular the startup time of the JVM.
It’s self-evident that if the bytecode methods have already been compiled, the application should run faster. The JVM doesn’t need to use extra CPU cycles profiling the code and running the JIT. The JVM can just load and run the previously compiled methods. An application can reach its peak runtime performance much faster because the JVM doesn’t need to warm up.
AOT Types
There are two main ways in which we can do AOT:
-
Static AOT involves compiling every method in a set of classes before running the code. This preliminary step creates a library of pre-compiled native machine code that is loaded at runtime by a JVM. This is similar to pre-compiling native methods into a loadable library.
-
Dynamic AOT involves compiling methods at runtime. The application must be profiled during runtime to determine which methods should be compiled.
How Does AOT Work?
AOT compilation was introduced into JDK 9 as an experimental feature with JEP 295: Ahead-of-Time Compilation.
AOT compilation is done with a new tool called jaotc
. This is a static AOT compiler that generates native code as shared libraries for the Java methods in specified class files. The JVM can load these AOT libraries and directly call the native code. There is no need for the JIT compiler to compile bytecode into native code.
During JVM startup the AOT initialization code looks for shared libraries in specific locations, or as specified with the AOTLibrary
command line flag. If any compiled libraries are found, they are used. If none are found then AOT is disabled for the current JVM instance.
The AOT compiler uses Graal as the back-end to generate the native code. The Graal compiler is written in Java, and is called using the JVM Compiler Interface (JVMCI).
The Graal compiler was used as an experimental JIT compiler in JDK 10 via JEP 317: Experimental Java-Based JIT Compiler.
AOT Compilation Process
Before we can use the AOT compiler, we must compile the class(es) as usual with the Java compiler:
javac HelloWorld.java
We then pass the compiled class file (containing bytecodes) to the jaotc
AOT compiler. This compiles the bytecodes into a library containing native machine code:
jaotc --output libHelloWorld.so HelloWorld.class
This library is then used when running the application:
java -XX:AOTLibrary=./libHelloWorld.so HelloWorld
When we run an application with this command, the JVM will automatically use the AOT compiled libraries. If we find that our application starts up more slowly, runs slower than expected or even crashes, we can switch AOT off with the -XX:-UseAOT flag
, or we can just delete the AOT libraries.
The same JDK configuration must be used during both AOT compile time and runtime. The Java version is registered in the AOT libraries and checked when loading. AOT recompilation is needed when the Java version is changed and/or when we make changes to the Java source code files (obviously).
Removal of AOT from OpenJDK
The Java-based AOT and JIT compilers were experimental features that did not see much community adoption. They were optional features and were removed from JDK 16. JEP 410 removes these components from the JDK source code.
GraalVM
Even though the AOT compiler has been removed from OpenJDK it doesn’t mean it’s dead. Far from it! It’s alive and well in a number of Java projects. One of the leading AOT projects is Graal.
The Graal project originated in Oracle Labs and is not part of OpenJDK. The Graal project has a number of tools including a full VM called GraalVM with a high performance JIT compiler and a number of monitoring and development tools.
The GraalVM Native Image tool is a stand-alone Java AOT compiler that produces native code executable files. These provide a number of advantages over Java’s JIT compiler: faster startup, smaller file sizes, lower memory and CPU usage, and improved security. These executables can be used in stand-alone Java applications; inside frameworks like Spring; inside containers like Docker; in the cloud, and as microservices.
Ending Off
In next week’s post, we’ll look at some features of the garbage collectors that are available in the JVM.
Don’t forget to share your comments and Java experiences.
Stay safe, and I’ll see you next week!