The purpose of the Leyden Early Access 2 Release is to prototype improvements to the startup time, time to peak performance, and footprint of Java programs, as a part of Project Leyden. We solicit feedback from the Java community, with the hope that some of these improvements can eventually be incorporated into future JDK releases.
-
This release contains experimental and unstable code from the Leyden "premain" prototype. It is not intended to be used in a production environment.
-
The experimental features in this release may be changed or removed without notice. Command line flags and workflows will change.
As of JDK 25, the Leyden Project has successfully delivered ahead-of-time (AOT) optimizations JEPs:
- JEP 483 - Ahead-of-Time Class Loading & Linking
- JEP 514 - Ahead-of-Time Command-Line Ergonomics
- JEP 515 - Ahead-of-Time Method Profiling
Please refer to the above JEPs for a detailed discussion of AOT optimizations.
The Leyden "premain" prototype includes new experimental AOT optimizations that are not yet integrated into the JDK mainline:
-
Ahead-of-Time Code Compilation (JEP draft 8335368): Methods that are frequently used during the training run can be compiled and stored along with the AOT cache. As a result, as soon as the application starts up in the production run, its methods can be natively executed.
- This feature is enabled by default when you create an AOT cache. It can be disabled with the diagnostic
flag
-XX:-AOTCodeCaching.
- This feature is enabled by default when you create an AOT cache. It can be disabled with the diagnostic
flag
-
Ahead-of-time generation of Dynamic Proxies: Dynamic proxies are frequently used by popular application frameworks. We can improve start-up time by generating these proxies ahead of time.
- This feature is enabled by default when you create an AOT cache. It can be disabled with the diagnostic
flag
-XX:-ArchiveDynamicProxies.
- This feature is enabled by default when you create an AOT cache. It can be disabled with the diagnostic
flag
-
Ahead-of-time generation of reflection data: Reflection data (such as instances of
java.lang.reflect.Method) are generated by the JVM to supportjava.lang.reflectoperations. We can generate these ahead of time to improve start-up.- This feature is enabled by default when you create an AOT cache. It can be disabled with the diagnostic
flag
-XX:-ArchiveReflectionData.
- This feature is enabled by default when you create an AOT cache. It can be disabled with the diagnostic
flag
-
Class Not Found Cache: Sometimes application frameworks repeatedly try to load classes that do not exist. This optimization allows such failing lookups to be done quickly without repeatedly scanning the class path.
- This feature is enabled by default when you create an AOT cache. It can be disabled with the diagnostic
flag
-XX:-ArchiveLoaderLookupCache.
- This feature is enabled by default when you create an AOT cache. It can be disabled with the diagnostic
flag
The easiest way to try out the Leyden Early Access 2 features is to use the java program in the Leyden Early Access 2 Release with the -XX:AOTCache flag.
Here's a small benchmark that uses the JDK's built-in
JavaCompiler
class to compile some Java source files. This benchmark spends a significant amount of start-up time
setting up the classes used by JavaCompiler, so it will benefit from the Leyden features.
First, download JavacBenchApp.java and compile it into a JAR file.
(Remember to use the java program from the Leyden Early Access 2 Release.)
$ javac JavacBenchApp.java
$ jar cvf JavacBenchApp.jar JavacBenchApp*.class
added manifest
adding: JavacBenchApp$ClassFile.class(in = 1608) (out= 787)(deflated 51%)
adding: JavacBenchApp$FileManager.class(in = 2090) (out= 979)(deflated 53%)
adding: JavacBenchApp$SourceFile.class(in = 1351) (out= 671)(deflated 50%)
adding: JavacBenchApp.class(in = 7571) (out= 3302)(deflated 56%)
We can run this benchmark without any AOT optimizations. It takes 893 ms:
$ java -cp JavacBenchApp.jar JavacBenchApp 50
Generated source code for 51 classes and compiled them in 893 ms
To use AOT optimizations for JavacBenchApp, we should first perform a training run and
capture the profiling information into JavacBenchApp.aotconfig
$ java -XX:AOTMode=record -XX:AOTConfiguration=JavacBenchApp.aotconfig \
-cp JavacBenchApp.jar JavacBenchApp 50
$ ls -l JavacBenchApp.aotconfig
-rw-rw-r-- 1 iklam iklam 27652096 Mar 3 16:23 JavacBenchApp.aotconfig
With the JavacBenchApp.aotconfig file, we can create the AOT cache. This is called the assembly phase:
$ java -XX:AOTMode=create -XX:AOTConfiguration=JavacBenchApp.aotconfig \
-cp JavacBenchApp.jar -XX:AOTCache=JavacBenchApp.aot
$ ls -l JavacBenchApp.aot
-r--r--r-- 1 iklam iklam 42332160 Mar 3 16:58 JavacBenchApp.aot
Alternatively, you can also combine the training run and assembly phase with a single command:
$ java -XX:AOTCacheOutput=JavacBenchApp.aot \
-cp JavacBenchApp.jar JavacBenchApp 50
$ ls -l JavacBenchApp.aot
-r--r--r-- 1 iklam iklam 42332160 Mar 3 16:58 JavacBenchApp.aot
Now, we can make a production run of the program using the AOT cache JavacBenchApp.aot. It finishes in 423 ms, or more than twice as fast as
before.
$ java -XX:AOTCache=JavacBenchApp.aot -cp JavacBenchApp.jar JavacBenchApp 50
Generated source code for 51 classes and compiled them in 423 ms
By default, training runs end when the application terminates. You have two other options to end training runs:
-XX:AOTEndTrainingOnMethodEntry=<method1,method2,...>[,count=100]jcmd <pid> AOT.end_training
Note that -XX:AOTEndTrainingOnMethodEntry uses the same format as -XX:CompileOnly and the default count is 1.
See EndTrainingOnMethodEntry.java for a test case.
As mentioned below, parts or all of the AOT cache may be disabled under certain circumstances. This may lead
to lower performance than expected. To diagnose potential performance issues, you can add -Xlog:aot* to the
command line to see detailed information about what parts of the AOT cache are being utilized. For example, if the
the AOT-compiled code cannot be loaded, you will see a log message like this:
[0.008s][info][aot,codecache,init] Mapped 652184 bytes at address 0x00007f491005f028 from AOT Code Cache
[0.008s][info][aot,codecache,init] Loaded 439 AOT code entries from AOT Code Cache
[0.008s][info][aot,codecache,init] Unable to use AOT Code Cache.
When trying out this release, please pay attention to the following limitations.
The AOT-compiled code will be only used if the production run is on a machine with the same type of CPU as used in the training run and assembly phase. If this is not the case (for example, the production run is on a machine that has different AVX capabilities), the AOT-compiled code will be ignored.
The AOT cache generated by the Leyden Early Access 2 Release includes machine instructions that are specific to the garbage collector. We recommend that you explicitly specify the same collector during both training and production runs. For example, if you prefer to use the SerialGC:
# assembly phase.
$ java -XX:AOTMode=create -XX:AOTConfiguration=JavacBenchApp.aotconfig \
-cp JavacBenchApp.jar \
-XX:AOTCache=JavacBenchApp.aot -XX:+UseSerialGC
# production run
$ java -XX:AOTCache=JavacBenchApp.aot -XX:+UseSerialGC -cp JavacBenchApp.jar \
JavacBenchApp 50
Otherwise, the AOT cache may not be usable for the production run, leading to suboptimal performance. For example, sometimes you may perform the assembly phase run on a large development host, and then use a container to run the application in a small production node. In the following scenario, as the collector is not explicitly specified, the VM will automatically pick G1 for the assembly phase, and SerialGC for the production run (due to its limited amount of memory):
# Assembly phase (uses G1 by default)
$ java -XX:AOTMode=create -XX:AOTConfiguration=JavacBenchApp.aotconfig \
-cp JavacBenchApp.jar -XX:AOTCache=JavacBenchApp.aot
# Production run (uses SerialGC)
$ docker run --rm -v /repos/leyden/build/linux-x64/images/jdk:/jdk -v $(pwd):/test \
--memory=1024m \
container-registry.oracle.com/java/openjdk \
bash -c 'cd /test; ' \
'/jdk/bin/java -XX:AOTCache=JavacBenchApp.aot ' \
' -cp JavacBenchApp.jar JavacBenchApp 50'
[0.001s][error][aot] AOT cache has aot-linked classes. It cannot be used because
GC used during dump time (G1) is not the same as runtime (Serial)
[0.001s][error][aot] An error has occurred while processing the AOT cache.
[0.001s][error][aot] Unable to map shared spaces
Error occurred during initialization of VM
Unable to use AOT cache.
Currently, if you use any other garbage collector in combination with -XX:AOTMode or -XX:AOTCache, the VM will
exit with an error.
$ java -XX:AOTMode=record -XX:AOTConfiguration=JavacBenchApp.aotconfig \
-cp JavacBenchApp.jar -XX:+UseZGC JavacBenchApp 50
Error occurred during initialization of VM
Cannot create the AOT configuration file: UseCompressedClassPointers must be enabled,
and collector must be G1, Parallel, Serial, Epsilon, or Shenandoah
Please see the README.md file from the Leyden Repository for more information.