Support custom class loaders in AOT cache
ioi.lam at oracle.com
ioi.lam at oracle.com
Wed Jan 28 22:00:42 UTC 2026
We have been brainstorming about supporting custom class loaders in the
AOT cache. While the design is far from final, in our small group
discussions, we seem to be converging on this:
- Only custom class loaders that are known to produce *stable* results
can store classes in the AOT cache
- Stable result is roughly: when given a class name X,
loader.loadClass(X) will always return a class with the same shape
- Also, loader.loadClass(X) should not produce any observable side
effects, other than the fact that class X has been loaded. E.g., don't
set any static fields inside loadClass:
- It's completely up to the custom class loader to decide whether it
meets the AOT cache requirement.
Some examples:
- A URLClassLoader that loads from a fixed set of JAR files in the local
file system that are known to never change
- A code generator that always generates the same code shape given the
same class name
A counter example:
- A code generator that mixes code with a random seed
The handshake between the class loader and the AOT cache might look like
this:
URL[] urls = new URL[] {"foo.jar", "bar.jar"};
URLClassLoader loader = new URLClassLoader(urls);
String UID = "URLClassLoader$foo.jar:" + cksum("foo.jar") +
"$bar.jar:" + cksum("bar.jar");
loader.setAOTCompatible(UID);
loader.loadClass("com.foo.Foo");
loader.loadClass("com.bar.Bar");
In the training run, the JVM will store all classes loaded by this
loader into the AOT cache. These classes are tagged with the given UID.
In the production run, when setAOTCompatible(UID) is called, the JVM
checks if the AOT cache has any classes tagged with the UID. If so,
these classes are automatically loaded into the loader *without any
observable side effect*. Note that the usual handshake of
ClassLoader::{findClass, loadClass, defineClass}, etc, does not happen.
The classes simply appeared in the loader out of thin air.
The UID provides a way for the loader to identify itself, as well as
encoding the dependencies that were assumed during the training run. In
the above example, we use the checksum of each JAR file to make sure
that these files haven't changed (or disappeared).
Note that we don't actually cache the loader object itself. The loader
object will probably have references to environment states that cannot
be safely stored into the AOT cache. Also, the creation of the loader
during the training might produce side effects that cannot be easily
captured into the AOT cache.
We will likely have some restrictions on the behavior of the "AOT
compatible" loaders
- loader.setAOTCompatible() must be called before any class is defined
in this loader. Otherwise setAOTCompatible() will throw an
IllegalStateException
- Only classes with simple ProtectionDomains will be stored into the AOT
cache. For example, if the loader defines a class with a
ProtectionDomain that uses a signed code source, the class will be
excluded from the cache.
Some implementation details:
Ashutosh is working on a prototype. I think we can store the classes
into the AOT configuration file at the end of the training run:
- Store the Java mirror of the class into the AOT configuration file
(this requires https://github.com/openjdk/jdk/pull/29472 )
- Also save the ProtectionDomain in the mirror
In the assembly phase, load the classes of each UID into an instance of
jdk.internal.misc.CDS$UnregisteredClassLoader. This way we can handle
classes of the same name defined in two different UIDs.
If two UIDs have a parent/child relationship, we should recreate that
with the UnregisteredClassLoader. This is needed for constant pool
pre-linking..
The above are just my random notes. Please add your thoughts.
Thanks
- Ioi
More information about the leyden-dev
mailing list