[External] : Re: Classes used in method body are loaded lazily or eagerly depending on method return type
David Holmes
david.holmes at oracle.com
Thu May 25 02:57:28 UTC 2023
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