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