“new”在Java w.r.t.class加载器中有什么作用?

2022-09-02 10:41:36

我无法在JLS / JVMSpec中轻松找到它,也不能在SO中找到它。我敢肯定,一定是有人问过...

那么,“新”实际上做了什么呢?假设我们在 A 中实例化一个类 B:

class A {
    // ...
    new B();
    // ...
}

这是否等同于

class A {
    // ...
    A.class.getClassLoader().loadClass("B's canonical name").newInstance();
    // ...
}

?

它是否在每个环境中都像这样工作?

如果您能为我指出 JLS/JVMSpec 中的相应章节,我将不胜感激。谢谢!

编辑:当然我们不能调用呼叫,因为B尚未加载。JVM 必须根据 import 语句解析名称。B.class.getCanonicalName()loadClass()


答案 1

这是否等同于

class A {
    // ...
    A.class.getClassLoader().loadClass("B's canonical name").newInstance();
    // ...
}

?

不,并非总是如此。

对于给定的命名空间,类装入仅执行一次,除非先前已卸载有问题的命名空间。因此,在大多数情况下,等效表达式将仅执行一次。换句话说,如果您有两个表达式 - ,则只会执行一次。ClassA.class.getClassLoader().loadClass("B's canonical name")new A()loadClass

构造函数的调用被 JVM 视为方法调用,但这需要 Java 编译器的合作。JVM 和编译器必须遵守 Java 虚拟机规范的第 3.9 节,其中规定:

3.9 特别命名的初始化方法

在 Java 虚拟机级别,每个构造函数 (§2.12) 都显示为具有特殊名称的实例初始化方法。此名称由编译器提供。由于该名称不是有效的标识符,因此不能直接在用 Java 编程语言编写的程序中使用它。实例初始化方法只能在 Java 虚拟机中由调用专用指令调用,并且只能在未初始化的类实例上调用。实例初始化方法采用派生该方法的构造函数的访问权限 (§2.7.4)。<init><init>

类或接口最多有一个类或接口初始化方法,并通过调用该方法进行初始化 (§2.17.4)。类或接口的初始化方法是静态的,不带任何参数。它具有 特殊名称 。此名称由编译器提供。由于该名称不是有效的标识符,因此不能直接在用 Java 编程语言编写的程序中使用它。类和接口初始化方法由 Java 虚拟机隐式调用。它们从不直接从任何 Java 虚拟机指令调用,而只是作为类初始化过程的一部分间接调用。<clinit><clinit>

本节假定与相关类相关的对象对当前线程可用。一旦对象可用,将调用与具有正确参数集的构造函数相对应的方法。ClassClass<init>

如果尚未装入,则将使用哪个类装入器来装入类的问题有点不同,并且与new关键字无关。这取决于一个类如何引用另一个类,即符号引用是否需要在运行时常量池中解析?此上下文中的行为在 Java 虚拟机规范的第 5.3 节中定义:

5.3 创建和加载

创建以名称 N 表示的类或接口 C 包括在 Java 虚拟机的方法区域 (§3.5.4) 中构造特定于实现的 C 内部表示形式。类或接口的创建由另一个类或接口 D 触发,该类或接口 D 通过其运行时常量池引用 C。

...

Java 虚拟机使用以下三个过程之一来创建以 N 表示的类或接口 C:

  • 如果 N 表示非数组类或接口,则使用以下两种方法之一进行加载,从而创建 C:

    • 如果 D 是由引导类装入器定义的,那么引导类装入器将启动 C 的装入 (§5.3.1)。

    • 如果 D 是由用户定义的类装入器定义的,那么同一个用户定义的类装入器将启动 C 的装入 (§5.3.2)。

  • 否则,N 表示数组类。数组类由 Java 虚拟机 (§5.3.3) 直接创建,而不是由类装入器创建。但是,在创建数组类 C 的过程中使用了 D 的定义类装入器。

请注意上面的引文中的句子。在表达式的上下文中,加载封闭类的类装入器将负责根据VM规范进行装入;当然,这是假设封闭类不是由引导类装入器装入的。If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of Cnew A()A


答案 2

要跟进我的评论,请像这样

new A()

翻译为

0:  new #2; //class A
3:  dup
4:  invokespecial   #3; //Method A."<init>":()V
7:  pop

堆栈跟踪是:

  [1] java.net.URLClassLoader$1.run (URLClassLoader.java:202)
  [2] java.security.AccessController.doPrivileged (native method)
  [3] java.net.URLClassLoader.findClass (URLClassLoader.java:190)
  [4] sun.misc.Launcher$ExtClassLoader.findClass (Launcher.java:229)
  [5] java.lang.ClassLoader.loadClass (ClassLoader.java:307)
  [6] java.lang.ClassLoader.loadClass (ClassLoader.java:296)
  [7] sun.misc.Launcher$AppClassLoader.loadClass (Launcher.java:301)
  [8] java.lang.ClassLoader.loadClass (ClassLoader.java:248)
  [9] Loader.main (Loader.java:11)

所以我想你的猜测非常接近。


推荐