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:31:36 UTC 2023
----- Original Message -----
> From: "Сергей Цыпанов" <sergei.tsypanov at yandex.ru>
> To: "core-libs-dev" <core-libs-dev at 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();
> }
> }
> }
More information about the core-libs-dev
mailing list