如果从未使用过相应的 class,classloader 是否加载 class 文件?

Does the classloader load a class file if the corresponding class is never used?

为了让我的问题更清楚,请考虑以下用例:

假设有一个包允许在给定平台上执行一组操作,例如 class 可以在 Windows 上编辑注册表。 此包在其他平台上不存在,因为在其他操作系统上没有等效操作。

为简单起见,考虑

windows/Registry.java

package windows;

public class Registry {

  static Registry instance = null;
  static{
    System.out.println("print from static block");
  }

  private Registry() {
    System.out.println("Registry instance created!");
  }

  public static synchronized Registry getInstance() {
    if (null == instance) {
      instance = new Registry();
    }
    return instance;
  }

  public void foo() {
    System.out.println("foo called.");
  }
}

和 class 我将有条件地使用注册表的地方:main/Main.java

package main;

import windows.Registry;

public class Main {

  public static void test1(boolean onWindows) {
    if (onWindows) {
      Registry instance = Registry.getInstance();
      System.out.println("We are on Windows: ");
      instance.foo();
    } else {
      System.out.println("We are somewhere else!");
    }
  }

  public static void main(String[] args) {
    System.out.println("Entered main");
    boolean onWindows = args.length > 0 ? Boolean.parseBoolean(args[0]) : false;
    test1(onWindows);
  }
}

问题是,如果Main.class中没有显式执行函数或class,是否保证不会执行Registry.class中的代码?

我能够在多个桌面平台和不同的 java 版本上测试这个例子,但我想知道这种行为是否被记录在案并且可以依赖它,如果它因此是预期的行为也在其他平台上,例如 android 或 JRE 的嵌入式版本。

如果没有这样的保证,因为(可能是自定义的)class加载程序可能决定加载 class 路径中的所有 .class 文件,我可以假设如果 onWindows 为假并且我从 classpath?

中删除 Registry.class,代码将在没有 java.lang.NoClassDefFoundError 的情况下工作

到目前为止我观察到的行为是

rm -rf bld; mkdir bld && javac -d bld src/windows/Registry.java src/main/Main.java && java -classpath bld main.Main true
Entered main
print from static block
Registry instance created!
We are on Windows: 
foo called.

rm -rf bld; mkdir bld && javac -d bld src/windows/Registry.java src/main/Main.java && java -classpath bld main.Main false
Entered main
We are somewhere else!


rm -rf bld; mkdir bld && javac -d bld src/windows/Registry.java src/main/Main.java && rm -r bld/windows && java -classpath bld main.Main false
Entered main
We are somewhere else!

rm -rf bld; mkdir bld && javac -d bld src/windows/Registry.java src/main/Main.java && rm -r bld/windows && java -classpath bld main.Main true
Entered main
Exception in thread "main" java.lang.NoClassDefFoundError: windows/Registry
        at main.Main.test1(Main.java:9)
        at main.Main.main(Main.java:20)
Caused by: java.lang.ClassNotFoundException: windows.Registry
        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:520)
        ... 2 more

是否定义了这些行为(惰性 class 加载程序和删除 Registry.class)?

The question is, if no function or class is explicitly executed in Main.class, is it guaranteed that no code from Registry.class is executed?

这并没有直接触及 class 加载 ,而是 class 初始化 ,是来自 class 的任何代码被执行的第一个点。具体来说,静态初始化块和静态成员的初始化程序在此阶段执行。有问题的 class 此时必须已经加载和验证,但它可能在任意时间之前加载。

根据 JLS 12.4.1

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

因此,如果您从未实例化 class 或访问其任何静态方法或字段(读取作为“常量变量”的静态字段除外),则 [=41 中没有代码=] 将永远被执行。

但是 class 没有被初始化并不意味着不会尝试 加载 它。 JLS 不禁止实现预期地加载 classes。其实JLS 12.2.1具体说:

a class loader may cache binary representations of classes and interfaces, prefetch them based on expected usage, or load a group of related classes together.

因此,不,当class windows.Registry 无法加载,无论是否可以预期实际使用。但是,在适当的情况下,您 可以 依赖于 class 未被初始化,因此没有代码被执行。