Support custom class loaders in AOT cache
David Lloyd
david.lloyd at redhat.com
Thu Jan 29 18:48:40 UTC 2026
This is very exciting for Quarkus in particular! However one concern I have
is that custom class loaders are often not using the parent-delegation
model. Our custom class loaders may, in some cases, link directly to other
class loaders based on a graph of interdependencies rather than the classic
delegation tree.
In this situation, I would be concerned about constant pool pre-linking.
Could the linked constants perhaps somehow "remember" what class loader
they were previously linked against? One would not expect this to change in
a well-behaved class loader arrangement.
Thanks!
On Wed, Jan 28, 2026 at 4:01 PM <ioi.lam at oracle.com> wrote:
> 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
>
>
--
- DML • he/him
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/leyden-dev/attachments/20260129/d626a797/attachment.htm>
More information about the leyden-dev
mailing list