一尘不染

Java wrt类加载器中的“新”功能是什么?

java

我无法在JLS / JVMSpec或SO中轻松找到它。我确定一定有人问过…

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

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

这等于

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

是不是还是在每种环境下都无法正常工作?

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

编辑:我们肯定不能叫B.class.getCanonicalName()loadClass()打电话,因为B的尚未加载。JVM必须根据导入语句来解析名称。


阅读 193

收藏
2020-12-03

共1个答案

一尘不染

这等于

class A {
    // ...
    A.class.getClassLoader().loadClass("B's canonical

name”).newInstance();
// …
}

不,并非总是如此。

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

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

3.9特别命名的初始化方法

在Java虚拟机级别,每个构造函数(第2.12节)都作为具有特殊名称的实例初始化方法出现
<init>。该名称由编译器提供。因为该名称<init>
不是有效的标识符,所以不能直接用Java编程语言编写的程序中使用它。实例初始化方法只能由 invokespecial 指令在Java虚拟机内调用
,并且只能在未初始化的类实例上调用。实例初始化方法具有从其获得构造函数的访问权限(第2.7.4节)。

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

本节假定Class与该类有关的对象可用于当前线程。一旦Class对象是可用的,该方法<init>对应于与右组参数的构造,将被调用。

使用哪个类加载器加载类(如果尚未加载)的问题有些不同,并且与new关键字无关。它取决于一个类如何引用另一个类,即是否需要在运行时常量池中解析符号引用?Java虚拟机规范的第5.3节定义了这种情况下的行为:

5.3创建和加载

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

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

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

    • 如果D由引导类加载器定义,则引导类加载器将启动C的加载(第5.3.1节)。

    • 如果D是由用户定义的类加载器定义的,则该用户定义的类加载器将启动C的加载(第5.3.2节)。

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

注意If D was defined by a user-defined class loader, then that same user- defined class loader initiates loading of C上面引用中的句子。在表达式的上下文中new A(),加载了封闭类的类加载器将A根据VM Spec 进行加载;当然,假设引导类加载器未加载该封闭类。

2020-12-03