Support custom class loaders in AOT cache

ioi.lam at oracle.com ioi.lam at oracle.com
Thu Jan 29 21:59:28 UTC 2026


On 1/29/26 10:48 AM, David Lloyd wrote:
> 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.
>
My current proposal can't easily support circular dependencies between 
loaders. For example,

1. Subtype dependencies: A is a subtype of B, which is a subtype of C. A 
and C are defined in loader 1 and B is defined in loader 2.

2. Constant pool dependencies: A and B are defined in two different loaders

     class A {
         static void foo(B b) { b.bar(); }
     }

     class B extends A {
         static void b.bar();
     }

Perhaps we need a way to define a group of inter-related loaders like

AOTLoaderUtils.setAOTCompatibe(
     UID1, loader1,
     UID2, loader2,
     UID3, loader3,
     UID4, loader4)

So all these loaders are recorded and restored as a group?


> 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.
>
Each set of classes (defined by the same loader) remember all the other 
loaders that they have references to. This set will not be restored from 
the AOT cache unless all the other loaders are also restored from the 
AOT cache.

If the dependencies forms a tree, then you can restore the loaders 
individually, but in the correct order.

If there are circular dependencies, then you'd need to restore them 
together as a group.

Thanks

- Ioi



> 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
>     <https://urldefense.com/v3/__https://github.com/openjdk/jdk/pull/29472__;!!ACWV5N9M2RV99hQ!PWfJNuimWLiP5BnpyeWBxecYOMqoai-z0bnS7KRbYvXpfL-4lafPU6vAv_zR5RvCyQzly57FytzQaN9oRuY$>
>     )
>     - 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/05f68749/attachment.htm>


More information about the leyden-dev mailing list