Resolving super interface in different contexts depending on the child class
Vladimir Ivanov
vladimir.x.ivanov at oracle.com
Tue Feb 27 03:54:42 UTC 2024
> One more question here: should we add loader constraints to the Delayed
> class when it is loaded into the boot class loader, while the
> ScheduledFutureTask implementor is loaded into the application one?
There are no loader constraints needed in your particular case because
SimpleTest.compare() and ScheduledFutureTask.compareTo(Delayed) are
loaded by the very same class loader.
When caller and callee belong to different loaders, then all classes
mentioned in their signature are subject to loader constraints between
their loaders. JVMS-5.4.3 [1] has more details on the topic.
> Or do we need to rely on the fact that Delayed should also be loaded into
> the application loader by some specific condition?
There was a discussion on hotspot-compiler-dev at ojn mailing list some
time ago about similar problem discovered in JRuby [2].
There are multiple subtle scenarios when class loading may be initiated
(e.g., JIT-compilation initiates loading of all signature classes in the
root method).
But, as it works now, the only reliable way to avoid inlining problems
caused by not-yet-loaded signature classes in performance-critical code
is to eagerly load them (for example, in class initializer).
Best regards,
Vladimir Ivanov
[1]
https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-5.html#jvms-5.3.4
[2]
https://mail.openjdk.org/pipermail/hotspot-compiler-dev/2020-September/039836.html
>
> сб, 24 февр. 2024 г. в 03:01, Vladimir Ivanov
> <vladimir.x.ivanov at oracle.com <mailto:vladimir.x.ivanov at oracle.com>>:
>
> Hi Aleksandr,
>
> I'd like to clarify the terminology: JVMS distinguishes initiating and
> defining loaders for a class (JVMS-5.3 [1]).
>
> The effects you are looking at are specific to initiating loaders.
>
> Even though a class is loaded only once (when well-behaved loaders are
> involved), it becomes visible in all descendant loaders on case-by-case
> basis. Each loader should initiate the loading to resolve the class in
> its context. (On implementation level it triggers the update of class
> loader dictionary.)
>
> Classes are loaded on-demand and precise conditions when it happens in
> practice may be far from evident. As an example, I played with the
> sketch you provided and here are my observations (when
> ScheduledFutureTask implements ScheduledFuture):
>
>
> * for the following code shape, bytecode verifier triggers class
> loading of Delayed class as part of invokevirtual verification (it
> needs
> to prove ScheduledFutureTask <: Delayed):
>
> public static int compare(ScheduledFutureTask o1,
> ScheduledFutureTask o2) {
> return o1.compareTo(o2);
> }
>
>
> * if compare is adjusted as follows, then checkcast instruction
> triggers class loading for non-null object during execution:
>
> public static int compare(ScheduledFutureTask o1, Object o2) {
> return o1.compareTo((Delayed)o2);
> }
>
> You mentioned that Delayed interface is not loaded by application
> loader
> in the first case, but I wasn't able to reproduce it based on your
> description. Please, share more details.
>
>
> Personally, I was able to avoid class loading (and trigger
> has_unloaded_classes_in_signature() == false for compareTo(Delayed))
> either by turning off verification (-Xverify:none) or always passing
> null as o2 [2]:
>
> public static void main(String[] args) {
> ScheduledFutureTask instances0 = new ScheduledFutureTask();
> for (int i = 0; i < 20_000; i++) {
> compare(instances0, null);
> }
> }
>
> The issue with null argument is known (inlining failures due to
> unloaded
> class in method signature when corresponding values are always null).
> JDK-8294609 [3] improved the situation for most of scenarios, but
> covering nulls require more work. It may be improved in the future
> release.
>
> Best regards,
> Vladimir Ivanov
>
> [1]
> https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-5.html#jvms-5.3 <https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-5.html#jvms-5.3>
>
> [2]
> https://gist.github.com/iwanowww/c33e49092bfa0e6a052f28ae139b55eb
> <https://urldefense.com/v3/__https://gist.github.com/iwanowww/c33e49092bfa0e6a052f28ae139b55eb__;!!ACWV5N9M2RV99hQ!JV9ZHd2pS1ntk1CM5Bdt4L7JU3zmTFW6NJJ2AN1GrxpreRDXRMIxojeDyRV9-pcnFRqHPEO1xQxiVIR37AQ4S1QvlA0$>
>
> [3] https://bugs.openjdk.org/browse/JDK-8294609
> <https://bugs.openjdk.org/browse/JDK-8294609>
>
> On 2/23/24 08:32, Aleksandr Popov wrote:
> > Hi! In the following test case I've investigated how Hotspot loads
> > classes into the loaders.
> > And found out that the inheritance chain could affect the result.
> >
> >
> //--------------------------------------------------------------------------
> > public class SimpleTest {
> > public static int compare(ScheduledFutureTask o1,
> > ScheduledFutureTask o2) {
> > return o1.compareTo(o2);
> > }
> >
> > public static void main(String[] args) {
> > ScheduledFutureTask instances0 = new ScheduledFutureTask();
> > ScheduledFutureTask instances1 = new ScheduledFutureTask();
> > compare(instances0, instances1);
> > }
> > }
> >
> > final class ScheduledFutureTask implements ScheduledFuture<Void> {
> > ...
> > @Override
> > public int compareTo(Delayed o) {
> > if (this == o) {
> > return 0;
> > }
> > }
> > }
> >
> //--------------------------------------------------------------------------
> >
> > * If /ScheduledFutureTask/ class implements
> > /java.util.concurrent.ScheduledFuture /interface,
> >
> > which implements /java.util.concurrent.Delayed/ interface:
> *[application
> > class -> core class -> core class],*
> > then the /Delayed /interface would be loaded into the boot class
> loader,
> > since it's resolved in the context of the core class.
> >
> > I've added some logs to track class resolving:
> > # parse interfaces of class ScheduledFutureTask, [APP] loader
> > # parse interfaces of class java/util/concurrent/ScheduledFuture,
> [BOOT]
> > loader
> > # parse interfaces of class java/util/concurrent/Delayed, [BOOT]
> loader
> > # Adding java/util/concurrent/Delayed to dictionary [BOOT]
> >
> > * If we replace core class in the chain with an application one:
> > *[application class -> application class -> core class], *
> >
> > then the /Delayed /interface would be loaded into both class
> loaders,
> > since it would be resolved in the context of the application class:
> >
> > # parse interfaces of class ScheduledFutureTask, [application
> class loader]
> > # parse interfaces of class Interface1, [application class loader]
> > # parse interfaces of class java/util/concurrent/Delayed, [boot
> class
> > loader]
> > # Adding java/util/concurrent/Delayed to dictionary [boot class
> loader]
> > # Adding java/util/concurrent/Delayed to dictionary [application
> class
> > loader]
> >
> > * Theoretically, this difference could affect the result of the
> > has_unloaded_classes_in_signature() call
> >
> > for the ScheduledFutureTask::compareTo method.
> >
> > final class ScheduledFutureTask implements ScheduledFuture<Void> {
> > ...
> > @Override
> > public int compareTo(Delayed o) {
> > if (this == o) {
> > return 0;
> > }
> > }
> > }
> >
> > Because in the first case (application class -> core class -> core
> > class) we have unloaded /java/util/concurrent/Delayed/
> > interface in the context of the accessing class, which was loaded
> into
> > the application class loader.
> >
> > So my question is: why is it correct to have class loaded into
> different
> > class loaders depending on the inheritance chain?
> > Or maybe it's intentionally resolved somewhere in the code?
> >
> > Note that SystemDictionary::check_signature_loaders doesn't add
> loader
> > constraints to the interface, since the caller and callee loaders
> are
> > the same in this case: both application loaders.
> >
> > Thank you!
>
More information about the hotspot-dev
mailing list