Summary
Compile an application's Java classes to native code prior to launching the virtual machine.
Goals
Improve the start-up time of small or large Java applications, with at most a limited impact on peak performance.
Change the end user's work flow as little as possible.
Non-Goals
It is not necessary to provide an explicit, exposed library-like mechanism for saving and loading compiled code.
Motivation
JIT compilers are fast, but Java programs can become so large that it takes a long time for the JIT to warm up completely. Infrequently-used Java methods might never be compiled at all, potentially incurring a performance penalty due to repeated interpreted invocations.
Description
The usage of AOT-compiled code is completely transparent to end users, but JVM options to control the use and storage of compiled code are provided.
For the initial release the only supported module will be java.base
. This is done to limit the problem space, since the Java code in java.base
is well-known in advance and can be thoroughly tested internally. AOT compilation of any other JDK module, or of user code, is experimental.
AOT compilation is done by new tool: jaotc:
jaotc --output libHelloWorld.so HelloWorld.class
It uses Graal as the code-generating backend.
The following jaotc flags are available:
--module <name> Module to compile
--output <file> Output file name
--compile-commands <file> Name of file with compile commands
--compile-for-tiered Generated profiling code for tiered compilation
--classpath <path> Specify where to find user class files
--threads <number> Number of compilation threads to be used
--ignore-errors Ignores all exceptions thrown during class loading
--exit-on-error Exit on compilation errors
--info Print information during compilation
--verbose Print verbose information
--debug Print debug information
--help Print this usage message
--version Version information
-J<flag> Pass <flag> directly to the runtime system
The following JVM product
command line -XX: flags are available for AOT:
+/-UseAOT - Use AOT-compiled files
+/-PrintAOT - Print used AOT klasses and methods
AOTLibrary=<file> - Specify the AOT library file
Additionally, these notproduct
/develop
flags are available:
PrintAOTStatistics - Print AOT statistics
UseAOTStrictLoading - Exit the VM if any of the AOT libraries has invalid config
The default setting of UseAOT
is true. During JVM startup the AOT initialization code looks for well-known shared libraries in a well-known location. If shared libraries are found, these are picked up and used. If no shared libraries can be found, AOT will be turned off for this JVM instance going forward.
The logging of AOT runtime events is integrated with JEP 271: Unified GC Logging. The following Unified Logging tags are supported:
aotclassfingerprint
aotclassload
aotclassresolve
AOT libraries can be compiled in two modes:
Non-tiered AOT compiled code behaves similarly to statically compiled C++ code in that no profiling information is collected and no JIT recompilations will happen.
Tiered AOT compiled code does collect profiling information. The profiling done is the same as the simple profiling done by C1 methods compiled at Tier 2. If AOT methods hit the AOT invocation thresholds these methods are being recompiled by C1 at Tier 3 first in order to gather full profiling information. This is required for C2 JIT recompilations to be able to produce optimal code and reach peak application performance.
The extra step of recompiling code at Tier 3 is necessary since the overhead of full profiling is too high to be used for all methods, especially for a module such as java.base
. For user applications it might make sense to allow AOT compilations with Tier 3-equivalent profiling, but this will not be supported for JDK 9.
The logical compilation mode for java.base
is tiered AOT since JIT recompilation of java.base
methods is desired to reach peak performance. Only in certain scenarios does a non-tiered AOT compilation make sense. This includes applications which require predictable behavior, when footprint is more important than peak performance, or for systems where dynamic code generation is not allowed. In these cases, AOT compilation needs to happen on the whole application code and is thus experimental in JDK 9.
The container format used for AOT-compiled code is shared libraries. The JDK 9 version only supports Linux/x64, and so the used shared library format is ELF.
Since class bytecodes can change either through changes to the source code or via class transformation and redefinition, the JVM needs to detect such changes and reject AOT-compiled code if the bytecode doesn't match. This is achieved with class fingerprinting. During AOT compilation a fingerprint for each class is generated and stored in the data section of the shared library. Later, when a class is loaded and AOT-compiled code is found for this class, the fingerprint for the current bytecode is compared to the one stored in the shared library. If there is a mismatch then the AOT code for that particular class is not used.
Alternatives
The saving of profiles or compilation decisions has been discussed, but that does nothing to reduce the time actually spent in the compiled code. It is possible that saving a very late copy of the low-level IR could be done instead, but that seems no less complex.
Testing
New AOT jtreg tests were developed for testing AOT functionality.
All existing tests can be run with an AOT-enabled JDK. This is already done as separate nightly testing configurations.
The other configuration runs all tests on an AOT-enabled JDK with an AOT-compiled java.base
module.
Risks and Assumptions
It’s possible that the use of pre-compiled code could result in less-than-optimal code being used, resulting in a loss of performance. Performance testing shows that some applications benefit from AOT-compiled code while others clearly show regressions. Since the AOT feature is an opt-in feature, possible performance regressions with user applications are avoidable. If a user finds that an application starts up more slowly, or doesn't reach the expected peak performance, they can just rebuild a new JDK without AOT libraries.
Dependences
This project depends on JEP 243: Java-Level JVM Compiler Interface since the AOT compiler uses Graal as the code-generating backend, which in turn depends on JVMCI.
The project depends on Graal-core to be part of OpenJDK. It should be included into OpenJDK build process and to be installed into resulting JDK binaries.