Classes used in method body are loaded lazily or eagerly depending on method return type

Сергей Цыпанов sergei.tsypanov at yandex.ru
Tue May 23 11:20:44 UTC 2023


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