[External] : Re: Classes used in method body are loaded lazily or eagerly depending on method return type

David Holmes david.holmes at oracle.com
Fri May 26 06:10:09 UTC 2023


On 25/05/2023 7:21 pm, Raffaello Giulietti wrote:
> 
> Yes, ChildClass.class is removed from the filesystem.
> 
> 
> And here's, the relevant info when running with -Xlog:class+init, 
> showing that verification succeeds for both TestLoading$ObjectReturner 
> and TestLoading$BaseClassReturner:
> 
> loading: TestLoading$ObjectReturner...
> [0.039s][info][class,init] Start class verification for: 
> TestLoading$ObjectReturner
> [0.039s][info][class,init] End class verification for: 
> TestLoading$ObjectReturner
> [0.039s][info][class,init] 500 Initializing 
> 'TestLoading$ObjectReturner'(no method) (0x0000000801001800)
> loading: TestLoading$BaseClassReturner...
> [0.039s][info][class,init] Start class verification for: 
> TestLoading$BaseClassReturner
> [0.039s][info][class,init] End class verification for: 
> TestLoading$BaseClassReturner
> [0.039s][info][class,init] 501 Initializing 
> 'TestLoading$BaseClassReturner'(no method) (0x0000000801001a08)

Can you enable -xlog:verification and class+load too please.

Thanks,
David
-----

> 
> 
> 
> On 2023-05-25 04:57, David Holmes wrote:
>> On 25/05/2023 12:34 am, Raffaello Giulietti wrote:
>>> As mentioned in my previous email, if you move the member class 
>>> ChildClass out of TestLoading (out of the nest), and make it a 
>>> top-level class like
>>>
>>>      public class ChildClass extends TestLoading.BaseClass {
>>>      }
>>>
>>> and change
>>>
>>>      URL classFileB = 
>>> TestLoading.class.getResource(TestLoading.class.getSimpleName() + 
>>> "$ChildClass.class");
>>>
>>> to
>>>
>>>      URL classFileB = 
>>> TestLoading.class.getResource(ChildClass.class.getSimpleName() + 
>>> ".class");
>>>
>>> rebuild everything and run, nothing is thrown.
>>>
>>>      deleting: <path-to>/ChildClass.class
>>>      loading: TestLoading$ObjectReturner...
>>>      loading: TestLoading$BaseClassReturner...
>>>
>>> I can't see any substantial difference, except that the nest rooted 
>>> at TestLoading lacks a member in the original setup and lacks nothing 
>>> in this setup.
>>>
>>> What's an explanation for this difference?
>>
>> Are you sure it actually deletes the file? What do you see when you 
>> enable class+load/init and verification logging?
>>
>> David
>> -----
>>
>>
>>
>>>
>>> Greetings
>>> Raffaello
>>>
>>>
>>> On 2023-05-24 00:35, Remi Forax wrote:
>>>>
>>>>
>>>> ----- Original Message -----
>>>>> From: "David Holmes" <david.holmes at oracle.com>
>>>>> To: "Raffaello Giulietti" <raffaello.giulietti at oracle.com>, 
>>>>> "core-libs-dev" <core-libs-dev at openjdk.org>
>>>>> Sent: Wednesday, May 24, 2023 12:23:24 AM
>>>>> Subject: Re: Classes used in method body are loaded lazily or 
>>>>> eagerly depending on method return type
>>>>
>>>>> On 24/05/2023 12:50 am, Raffaello Giulietti wrote:
>>>>>> I think the problem here is that you are deleting a class in a nest.
>>>>>>
>>>>>> During the verification of BaseClassReturner.getObject(), access 
>>>>>> control
>>>>>> (JVMS, 5.4.4, second half) determines that the nest is broken, as
>>>>>> ChildClass is not present anymore.
>>>>>
>>>>> Not sure access control gets involved at this stage of the 
>>>>> verification
>>>>> process. But in any case turning on logging does not show anything
>>>>> related to nestmates happening between BaseClass and ChildClass. It
>>>>> seems to just be the resolution of the return type during verification
>>>>> of the method, that causes the loading of ChildClass and the 
>>>>> subsequent
>>>>> CNFE if it has been removed.
>>>>>
>>>>>> If you move ChildClass out of TestLoading so that it becomes a 
>>>>>> top-level
>>>>>> class extending TestLoading.BaseClass, and if you adapt the line that
>>>>>> initializes the local var classFileB to refer to the new location, 
>>>>>> the
>>>>>> code will not throw, despite ChildClass being deleted.
>>>>>
>>>>> My simplified test shows it still throws when verifying 
>>>>> BaseClassReturner.
>>>>
>>>> Nestmate checking is done lazily, so if you do not call a 
>>>> method/access a field of a nested class, the VM should not trigger a 
>>>> class loading.
>>>>
>>>> Moreover, if you test with Java 8 (nestmates were introduced in Java 
>>>> 11), it failed too.
>>>> That's another clue that the error is not related to nestmates 
>>>> checking.
>>>>
>>>>>
>>>>>
>>>>> Cheers,
>>>>> David
>>>>
>>>> regards,
>>>> Rémi
>>>>
>>>>>
>>>>>>
>>>>>> Greetings
>>>>>> Raffaello
>>>>>>
>>>>>>
>>>>>>
>>>>>> On 2023-05-23 13:20, Сергей Цыпанов wrote:
>>>>>>> Hello,
>>>>>>>
>>>>>>> originally this question was asked here:
>>>>>>> https://urldefense.com/v3/__https://stackoverflow.com/q/76260269/12473843__;!!ACWV5N9M2RV99hQ!Mb5nhj7EbuftWzF7s4GX9auUZZlyPyCUnLs64c4mkmSGJm4pw0CNgRzQR5wOYuApyE_kHSAnVxGyTM9PHz5StCppGw$ ,
>>>>>>> the code sample for reproduction will be put below or can be 
>>>>>>> found via
>>>>>>> the link
>>>>>>>
>>>>>>> The issue is about eager/lazy loading of a class depending on method
>>>>>>> return type.
>>>>>>> If one runs the code below with Java 11-19 it will fail with
>>>>>>> NoClassDefFoundError (this is expected as delete class file for
>>>>>>> ChildClass):
>>>>>>>
>>>>>>> java.lang.NoClassDefFoundError: org/example/TestLoading$ChildClass
>>>>>>>      at java.base/java.lang.Class.forName0(Native Method)
>>>>>>>      at java.base/java.lang.Class.forName(Class.java:390)
>>>>>>>      at java.base/java.lang.Class.forName(Class.java:381)
>>>>>>>      at org.example.TestLoading.loadMyClass(TestLoading.java:29)
>>>>>>>      at org.example.TestLoading.main(TestLoading.java:23)
>>>>>>> Caused by: java.lang.ClassNotFoundException:
>>>>>>> org.example.TestLoading$ChildClass
>>>>>>>      at
>>>>>>> java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
>>>>>>>      at
>>>>>>> java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
>>>>>>>      at 
>>>>>>> java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
>>>>>>>      ... 5 more
>>>>>>>
>>>>>>> As of Java 20 chapter 12.4.1 of JLS states:
>>>>>>> ---------------------------------------------------------
>>>>>>> A class or interface T will be initialized immediately before the
>>>>>>> first occurrence of any one of the following:
>>>>>>>
>>>>>>> - T is a class and an instance of T is created.
>>>>>>> - a static method declared by T is invoked.
>>>>>>> - a static field declared by T is assigned.
>>>>>>> - a static field declared by T is used and the field is not a 
>>>>>>> constant
>>>>>>> variable (§4.12.4).
>>>>>>>
>>>>>>> When a class is initialized, its superclasses are initialized (if 
>>>>>>> they
>>>>>>> have not been previously initialized), as well as any 
>>>>>>> superinterfaces
>>>>>>> (§8.1.5) that declare any default methods (§9.4.3) (if they have not
>>>>>>> been previously initialized).
>>>>>>> Initialization of an interface does not, of itself, cause
>>>>>>> initialization of any of its superinterfaces.
>>>>>>> A reference to a static field (§8.3.1.1) causes initialization of 
>>>>>>> only
>>>>>>> the class or interface that actually declares it, even though it 
>>>>>>> might
>>>>>>> be referred to through the name of a subclass, a subinterface, or a
>>>>>>> class that implements an interface.
>>>>>>> Invocation of certain reflective methods in class Class and in 
>>>>>>> package
>>>>>>> java.lang.reflect also causes class or interface initialization.
>>>>>>> A class or interface will not be initialized under any other
>>>>>>> circumstance.
>>>>>>> ---------------------------------------------------------
>>>>>>> With the code snippet we see that calling
>>>>>>> Class.forName(ObjectReturner.class.getName()) succeeds and
>>>>>>> Class.forName(BaseClassReturner.class.getName()) fails even though
>>>>>>> both declare returning an instance of ChildClass.
>>>>>>> This failure is unexpected as in the code below we don't fulfill any
>>>>>>> requirement for class loading as of JLS 12.4.1, but the JVM still
>>>>>>> tries to load the class.
>>>>>>>
>>>>>>> I suspect it might be related to class file validation and/or
>>>>>>> security, because when we run the code with -Xlog:class+init 
>>>>>>> there's a
>>>>>>> reference to LinkageError in loading log:
>>>>>>>
>>>>>>> loading: org.example.TestLoading$BaseClassReturner...
>>>>>>> [0.277s][info][class,init] Start class verification for:
>>>>>>> org.example.TestLoading$BaseClassReturner
>>>>>>> [0.277s][info][class,init] 771 Initializing
>>>>>>> 'java/lang/ReflectiveOperationException'(no method) 
>>>>>>> (0x0000000800004028)
>>>>>>> [0.277s][info][class,init] 772 Initializing
>>>>>>> 'java/lang/ClassNotFoundException'(no method) (0x0000000800004288)
>>>>>>> [0.277s][info][class,init] 773 Initializing
>>>>>>> 'java/lang/LinkageError'(no method)
>>>>>>> (0x00000008000044f8)                                  <----
>>>>>>> [0.277s][info][class,init] 774 Initializing
>>>>>>> 'java/lang/NoClassDefFoundError'(no method) (0x0000000800004758)
>>>>>>> [0.277s][info][class,init] Verification for
>>>>>>> org.example.TestLoading$BaseClassReturner has exception pending
>>>>>>> 'java.lang.NoClassDefFoundError org/example/TestLoading$ChildClass'
>>>>>>> [0.277s][info][class,init] End class verification for:
>>>>>>> org.example.TestLoading$BaseClassReturner
>>>>>>>
>>>>>>>
>>>>>>> So I've got three questions about this:
>>>>>>> - Does class loading depend on method's return type?
>>>>>>> - Which part of JLS/JVM spec describes eager class loading in 
>>>>>>> this case?
>>>>>>> - Could one point out the particular piece of the VM code 
>>>>>>> responsible
>>>>>>> for class loading in this case?
>>>>>>>
>>>>>>> Regards,
>>>>>>> Sergey Tsypanov
>>>>>>>
>>>>>>> Code snippet for reproduction:
>>>>>>>
>>>>>>> public class TestLoading {
>>>>>>>
>>>>>>>     public static void main(String[] args) throws Exception {
>>>>>>>       Class.forName(BaseClass.class.getName());
>>>>>>>       URL classFileB =
>>>>>>> TestLoading.class.getResource(TestLoading.class.getSimpleName() +
>>>>>>> "$ChildClass.class");
>>>>>>>       if (classFileB != null) {
>>>>>>>         if (!"file".equals(classFileB.getProtocol())) {
>>>>>>>           throw new UnsupportedOperationException();
>>>>>>>         }
>>>>>>>         Path path = new File(classFileB.getPath()).toPath();
>>>>>>>         System.out.println("deleting: " + path);
>>>>>>>         Files.delete(path);
>>>>>>>       }
>>>>>>>
>>>>>>>       loadMyClass(ObjectReturner.class.getName());
>>>>>>>       loadMyClass(BaseClassReturner.class.getName());
>>>>>>>     }
>>>>>>>
>>>>>>>     private static void loadMyClass(String name) {
>>>>>>>       System.out.println("loading: " + name + "...");
>>>>>>>       try {
>>>>>>>         Class.forName(name);
>>>>>>>       } catch (Throwable e) {
>>>>>>>         e.printStackTrace(System.out);
>>>>>>>       }
>>>>>>>     }
>>>>>>>
>>>>>>>     public static class BaseClass {
>>>>>>>     }
>>>>>>>
>>>>>>>     public static class ChildClass extends BaseClass {
>>>>>>>     }
>>>>>>>
>>>>>>>     public static class ObjectReturner {
>>>>>>>       public Object getObject() {
>>>>>>>         return new ChildClass();
>>>>>>>       }
>>>>>>>     }
>>>>>>>
>>>>>>>     public static class BaseClassReturner {
>>>>>>>       public BaseClass getObject() {
>>>>>>>         return new ChildClass();
>>>>>>>       }
>>>>>>>     }
>>>>>>> }


More information about the core-libs-dev mailing list