[External] : Re: Classes used in method body are loaded lazily or eagerly depending on method return type
Raffaello Giulietti
raffaello.giulietti at oracle.com
Thu May 25 09:21:09 UTC 2023
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)
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