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

Raffaello Giulietti raffaello.giulietti at oracle.com
Fri May 26 18:06:31 UTC 2023


My bad.

In the modifications of the original code, I proposed to replace

     URL classFileB = 
TestLoading.class.getResource(TestLoading.class.getSimpleName() + 
"$ChildClass.class");

with

     URL classFileB = 
TestLoading.class.getResource(ChildClass.class.getSimpleName() + ".class");

However, this loads ChildClass just a few lines *before* it is removed 
from the filesystem, so later uses of the class in the same run, in 
particular during verification of method BaseClassReturner.getObject(), 
are immune to the removal.

Replacing with

     URL classFileB = 
TestLoading.class.getResource(TestLoading.class.getPackageName() + 
"ChildClass.class");

will remove the class without loading it, and will later throw, as in 
the original setup.


Sorry for the noise
Raffaello



On 2023-05-26 08:10, David Holmes wrote:
> 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