Resolving super interface in different contexts depending on the child class
Aleksandr Popov
a.popov.tmb at gmail.com
Mon Feb 26 23:49:55 UTC 2024
Vladimir, thank you for clarifying this.
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?
Or do we need to rely on the fact that Delayed should also be loaded into
the application loader by some specific condition?
сб, 24 февр. 2024 г. в 03:01, Vladimir Ivanov <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
>
> [2] https://gist.github.com/iwanowww/c33e49092bfa0e6a052f28ae139b55eb
>
> [3] 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!
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/hotspot-dev/attachments/20240227/4fa3c5d1/attachment-0001.htm>
More information about the hotspot-dev
mailing list