----- Original Message ----- > From: "Сергей Цыпанов" <sergei.tsypa...@yandex.ru> > To: "core-libs-dev" <core-libs-...@openjdk.java.net> > Sent: Tuesday, May 23, 2023 1:20:44 PM > Subject: Classes used in method body are loaded lazily or eagerly depending > on method return type
> Hello, 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. It also depends on the content of the methods. If you modify the code to return null, the NoClassDefFoundError disappears public static class ObjectReturner { public Object getObject() { return null; } } public static class BaseClassReturner { public BaseClass getObject() { return null; } } which means that the NoClassDefFoundError comes from the bytecode verifier. In your original ObjectReturner, the bytecode verifier needs to check if ChildClass is an Object and there is a shortcircuit for that, so ChildClass does not need to be loaded. But in BaseClassReturner, the bytecode verifier needs to check if ChildClass is a BaseClass, so it has to load ChildClass to know if the superclass is BaseClass. Another way to see that it's the verifier that triggers the class loading is to use the option "-noverify" java -noverify TestLoading should works ! regards, Rémi > 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(); > } > } > }