Classes used in method body are loaded lazily or eagerly depending on method return type
Remi Forax
forax at univ-mlv.fr
Tue May 23 22:35:06 UTC 2023
----- 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://stackoverflow.com/q/76260269/12473843,
>>> 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