为什么“在java虚拟机内部”会说“不需要加载新生儿”?

dpiehjr4  于 2021-07-04  发布在  Java
关注(0)|答案(2)|浏览(304)

在java虚拟机中——第7章类型的生存期——初始化的代码片段如下。

class NewParent {

    static int hoursOfSleep = (int) (Math.random() * 3.0);

    static {
        System.out.println("NewParent was initialized.");
    }
}

class NewbornBaby extends NewParent {

    static int hoursOfCrying = 6 + (int) (Math.random() * 2.0);

    static {
        System.out.println("NewbornBaby was initialized.");
    }
}

class Example2 {

    // Invoking main() is an active use of Example2
    public static void main(String[] args) {

        // Using hoursOfSleep is an active use of NewParent,
        // but a passive use of NewbornBaby
        int hours = NewbornBaby.hoursOfSleep;
        System.out.println(hours);
    }

    static {
        System.out.println("Example2 was initialized.");
    }
}

在上面的例子中,执行example2的main()只会初始化example2和newparent。新生婴儿未初始化,不需要加载。 Example2 参考文献 NewbornBaby ,我认为应该是“jvm加载” NewbornBaby 一开始它发现 NewbornBaby 没有 hoursOfSleep 字段,然后继续加载 NewbornBaby 的超类 NewParent ". 那么,为什么java虚拟机内部说不需要加载newbornbaby呢?
之后 javac Example2.java ,我跑 java -verbose:class Example2 ,下面是输出的一部分。

[Loaded Example2 from file:/Users/jason/trivial/]
[Loaded sun.launcher.LauncherHelper$FXHelper from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Class$MethodArray from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Void from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar]
Example2 was initialized.
[Loaded NewParent from file:/Users/jason/trivial/]
[Loaded NewbornBaby from file:/Users/jason/trivial/]
[Loaded java.lang.Math$RandomNumberGeneratorHolder from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.util.Random from /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar]
NewParent was initialized.
1

它表明jvm确实加载了 NewbornBaby .

a7qyws3x

a7qyws3x1#

在本例中,正如您在书中所引用的 NewbornBaby 一开始它发现 NewbornBaby 没有 hoursOfSleep 字段,然后继续加载 NewbornBaby 的超类 NewParent “:jvm尝试加载 NewbornBaby 但是这个类是 NewParent 所以要加载它需要加载它的超类的所有类方法,所以要加载 newbornBaby (一)必须加载 NewParent (二)作为装载的一部分 newbornBaby .
在恢复负荷时 NewParent ,看起来它是先加载的,但请记住,这是加载 newbornBaby (已启动的进程)

xj3cbfub

xj3cbfub2#

您遇到了类加载和初始化的常见混淆。
您链接的这篇文章介绍了由一些定义良好的操作触发的初始化:

§12.4.1. 初始化发生时

类或接口类型t将在第一次出现以下任一情况之前立即初始化: T 是的类和示例 T 已创建。
static 方法声明者 T 已调用。
static 字段声明者 T 已分配。
static 字段声明者 T 并且字段不是常量变量(§4.12.4).
您的代码正在访问 static 课堂上的场 NewParent 这将触发该类的初始化。你访问它的方式与此无关。因此,当您运行代码而不记录时,它会打印出来

Example2 was initialized.
NewParent was initialized.
1

所以呢 NewbornBaby 尚未初始化,因为未执行任何指定的触发器操作。
然而,类加载是完全不同的事情。它的时间是故意不指定的,只是它必须在初始化之前发生。jvm可能会急切地加载所有引用的类,甚至在应用程序启动之前,或者延迟加载,直到验证器或应用程序需要它为止。
在这一点上,理解编译器将检查 static 字段存在并将在类中找到它 NewParent ,它仍将使用源代码中使用的类型生成字节码。所以,加载指定的类 NewbornBaby 在运行时是不可避免的(文章在这方面是错误的),即使它不会得到初始化(这篇文章似乎与加载混淆)。
与jls相比, §13.1. 二进制的形式:
给定一个表示类中字段访问的合法表达式 C ,引用名为 f 它不是常量变量,并且在(可能不同的)类或接口中声明 D ,我们定义字段引用的限定类型如下:
...
如果引用的格式为typename .f ,其中typename表示类或接口,则typename表示的类或接口是引用的限定类型。
...
引用 f 必须编译成符号引用才能擦除(§4.6)引用的限定类型,加上字段的简单名称, f .
换句话说,这个表达式 NewbornBaby.hoursOfSleep 将使用 NewbornBaby 作为限定类型,运行时必须像编译器那样在父类型中再次找到实际字段。如果有不同版本的 NewbornBaby 在运行时具有该名称和类型的匹配字段时,将改用该字段。
没有办法加载类 NewbornBaby 在运行时,找出哪个场景适用。
此外,当类加载被记录时,它是不规范的。似乎,它不是在加载被触发时发生的,而是在加载完成时发生的。这已经包括了一些验证步骤,包括加载和检查超类是否存在以及是否兼容(即不是 interface ,不是 final 等)。
所以当验证器遇到访问类 NewbornBaby ,它触发该类的加载,从而触发 NewParent . 但是 NewParent 首先完成并首先报告,因为它的完成是完成的加载所必需的 NewbornBaby 之后会记录下来。
但是,如前所述,这是特定于实现的。只精确指定了初始化。

相关问题