<div dir="ltr">Vladimir, thank you for clarifying this.<div><br><div>One more question here: should we add loader constraints to the Delayed </div><div>class when it is loaded into the boot class loader, while the </div><div>ScheduledFutureTask implementor is loaded into the application one?</div><div><br></div><div>Or do we need to rely on the fact that Delayed should also be loaded into </div><div>the application loader by some specific condition?<br></div></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">сб, 24 февр. 2024 г. в 03:01, Vladimir Ivanov <<a href="mailto:vladimir.x.ivanov@oracle.com">vladimir.x.ivanov@oracle.com</a>>:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Hi Aleksandr,<br>
<br>
I'd like to clarify the terminology: JVMS distinguishes initiating and <br>
defining loaders for a class (JVMS-5.3 [1]).<br>
<br>
The effects you are looking at are specific to initiating loaders.<br>
<br>
Even though a class is loaded only once (when well-behaved loaders are <br>
involved), it becomes visible in all descendant loaders on case-by-case <br>
basis. Each loader should initiate the loading to resolve the class in <br>
its context. (On implementation level it triggers the update of class <br>
loader dictionary.)<br>
<br>
Classes are loaded on-demand and precise conditions when it happens in <br>
practice may be far from evident. As an example, I played with the <br>
sketch you provided and here are my observations (when <br>
ScheduledFutureTask implements ScheduledFuture):<br>
<br>
<br>
  * for the following code shape, bytecode verifier triggers class <br>
loading of Delayed class as part of invokevirtual verification (it needs <br>
to prove ScheduledFutureTask <: Delayed):<br>
<br>
       public static int compare(ScheduledFutureTask o1,<br>
  ScheduledFutureTask o2) {<br>
           return o1.compareTo(o2);<br>
       }<br>
<br>
<br>
  * if compare is adjusted as follows, then checkcast instruction <br>
triggers class loading for non-null object during execution:<br>
<br>
     public static int compare(ScheduledFutureTask o1, Object o2) {<br>
         return o1.compareTo((Delayed)o2);<br>
     }<br>
<br>
You mentioned that Delayed interface is not loaded by application loader <br>
in the first case, but I wasn't able to reproduce it based on your <br>
description. Please, share more details.<br>
<br>
<br>
Personally, I was able to avoid class loading (and trigger <br>
has_unloaded_classes_in_signature() == false for compareTo(Delayed)) <br>
either by turning off verification (-Xverify:none) or always passing <br>
null as o2 [2]:<br>
<br>
     public static void main(String[] args) {<br>
         ScheduledFutureTask instances0 = new ScheduledFutureTask();<br>
         for (int i = 0; i < 20_000; i++) {<br>
             compare(instances0, null);<br>
         }<br>
     }<br>
<br>
The issue with null argument is known (inlining failures due to unloaded <br>
class in method signature when corresponding values are always null). <br>
JDK-8294609 [3] improved the situation for most of scenarios, but <br>
covering nulls require more work. It may be improved in the future release.<br>
<br>
Best regards,<br>
Vladimir Ivanov<br>
<br>
[1] <a href="https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-5.html#jvms-5.3" rel="noreferrer" target="_blank">https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-5.html#jvms-5.3</a><br>
<br>
[2] <a href="https://gist.github.com/iwanowww/c33e49092bfa0e6a052f28ae139b55eb" rel="noreferrer" target="_blank">https://gist.github.com/iwanowww/c33e49092bfa0e6a052f28ae139b55eb</a><br>
<br>
[3] <a href="https://bugs.openjdk.org/browse/JDK-8294609" rel="noreferrer" target="_blank">https://bugs.openjdk.org/browse/JDK-8294609</a><br>
<br>
On 2/23/24 08:32, Aleksandr Popov wrote:<br>
> Hi! In the following test case I've investigated how Hotspot loads <br>
> classes into the loaders.<br>
> And found out that the inheritance chain could affect the result.<br>
> <br>
> //--------------------------------------------------------------------------<br>
> public class SimpleTest {<br>
>      public static int compare(ScheduledFutureTask o1, <br>
> ScheduledFutureTask o2) {<br>
>          return o1.compareTo(o2);<br>
>      }<br>
> <br>
>      public static void main(String[] args) {<br>
>          ScheduledFutureTask instances0 = new ScheduledFutureTask();<br>
>          ScheduledFutureTask instances1 = new ScheduledFutureTask();<br>
>          compare(instances0, instances1);<br>
>      }<br>
> }<br>
> <br>
> final class ScheduledFutureTask implements ScheduledFuture<Void> {<br>
> ...<br>
>      @Override<br>
>      public int compareTo(Delayed o) {<br>
>          if (this == o) {<br>
>              return 0;<br>
>          }<br>
>      }<br>
> }<br>
> //--------------------------------------------------------------------------<br>
> <br>
>   * If /ScheduledFutureTask/ class implements<br>
>     /java.util.concurrent.ScheduledFuture /interface,<br>
> <br>
> which implements /java.util.concurrent.Delayed/ interface: *[application <br>
> class -> core class -> core class],*<br>
> then the /Delayed /interface would be loaded into the boot class loader, <br>
> since it's resolved in the context of the core class.<br>
> <br>
> I've added some logs to track class resolving:<br>
> # parse interfaces of class ScheduledFutureTask, [APP] loader<br>
> # parse interfaces of class java/util/concurrent/ScheduledFuture, [BOOT] <br>
> loader<br>
> # parse interfaces of class java/util/concurrent/Delayed, [BOOT] loader<br>
> # Adding java/util/concurrent/Delayed to dictionary [BOOT]<br>
> <br>
>   * If we replace core class in the chain with an application one:<br>
>     *[application class -> application class -> core class], *<br>
> <br>
> then the /Delayed /interface would be loaded into both class loaders, <br>
> since it would be resolved in the context of the application class:<br>
> <br>
> # parse interfaces of class ScheduledFutureTask, [application class loader]<br>
> # parse interfaces of class Interface1, [application class loader]<br>
> # parse interfaces of class java/util/concurrent/Delayed, [boot class <br>
> loader]<br>
> # Adding java/util/concurrent/Delayed to dictionary [boot class loader]<br>
> # Adding java/util/concurrent/Delayed to dictionary [application class <br>
> loader]<br>
> <br>
>   * Theoretically, this difference could affect the result of the<br>
>     has_unloaded_classes_in_signature() call<br>
> <br>
> for the ScheduledFutureTask::compareTo method.<br>
> <br>
> final class ScheduledFutureTask implements ScheduledFuture<Void> {<br>
> ...<br>
>      @Override<br>
>      public int compareTo(Delayed o) {<br>
>          if (this == o) {<br>
>              return 0;<br>
>          }<br>
>      }<br>
> }<br>
> <br>
> Because in the first case (application class -> core class -> core <br>
> class) we have unloaded /java/util/concurrent/Delayed/<br>
> interface in the context of the accessing class, which was loaded into <br>
> the application class loader.<br>
> <br>
> So my question is: why is it correct to have class loaded into different <br>
> class loaders depending on the inheritance chain?<br>
> Or maybe it's intentionally resolved somewhere in the code?<br>
> <br>
> Note that SystemDictionary::check_signature_loaders doesn't add loader <br>
> constraints to the interface, since the caller and callee loaders are <br>
> the same in this case: both application loaders.<br>
> <br>
> Thank you!<br>
</blockquote></div>