Java中的“新”做什么w.r.t.类加载器?

时间:2011-10-10 08:04:26

标签: java classloader

我不能在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必须根据import语句解析名称。

3 个答案:

答案 0 :(得分:15)

  

这相当于

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编程语言编写。实例初始化   方法只能在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由bootstrap类加载器定义,那么引导程序   类加载器启动加载C(第5.3.1节)。

    •   
    • 如果D是由用户定义的类加载器定义的,那么相同   用户定义的类加载器启动C(第5.3.2节)的加载。

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

  •   

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

答案 1 :(得分:3)

跟进我的评论,像

这样的行
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)

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

答案 2 :(得分:-1)

我发现这个链接主要解释了Java中“new”运算符的概念。我的主要想法是这句话:

"(..) The new operator instantiates a class by allocating memory for a new object and returning a reference to that memory. The new operator also invokes the object constructor. (..)"

我认为应该考虑三件事:

  1. “new”运算符在每个环境中都很常见,但由于开发人员需要它。在Java的情况下,“new”运算符也为对象分配内存空间。
  2. 有时(在较旧的编译器中)它不可用,并且所有声明都必须是“漫长的道路”。因此,为了兼容复古,两个语句都是等价的。
  3. 您可能需要覆盖“loadClass”或“getClassLoader()”
  4. "(..)Even though Java's new keyword is central to the language, there may be better ways of getting the job done.(..)"

    希望它有所帮助;)