问题描述
Inside the Java Virtual Machine - Chapter 7 The Lifetime of a Type - Initialization具有如下代码段。
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初始化。 NewbornBaby未初始化,不需要加载。
。 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
。
解决方法
您遇到了 loading 和 initialization 类的常见困惑。
您链接的文章介绍了初始化,该初始化由某些well defined actions触发:
§12.4.1。初始化发生时
T的类或接口类型T会在以下任何一种首次出现之前立即初始化:
T
是一个类,并且创建了T
的实例。- 调用由
static
声明的T
方法。- 分配了
static
声明的T
字段。- 使用了
static
声明的T
字段,该字段不是常量变量(§4.12.4)。
您的代码正在访问类static
中的NewParent
字段,这将触发该类的初始化。您访问它的方式是无关紧要的。因此,当您运行代码而不登录时,它会打印出来
Example2 was initialized.
NewParent was initialized.
1
由于未执行任何指定的触发动作,因此NewbornBaby
尚未初始化。
Class loading 是完全不同的事情。故意不指定其时间,只是必须在初始化之前进行。 JVM可能甚至在应用程序启动之前就急切地加载所有引用的类,或者推迟加载,直到验证者或应用程序需要它为止。
在这一点上,重要的是要理解,尽管编译器将检查所引用的static
字段是否存在并在类NewParent
中找到它,但它仍将使用具有以下内容的类型来生成字节码:已在源代码中使用。因此,即使在运行时加载指定的类NewbornBaby
也是不可避免的(本文对此是错误的),即使它不会被初始化(该文章似乎与加载混淆)。
与JLS, §13.1. The Form of a Binary比较:
给出一个表示在类
C
中的字段访问的合法表达式,该引用引用名为f
的字段,该字段不是常量变量,并且在(可能是不同的)类或接口{{1 }},我们定义字段引用的限定类型如下:...
- 如果引用的格式为 TypeName
D
,其中 TypeName 表示类或接口,则由 TypeName表示的类或接口是引用的限定类型。...
对
.f
的引用必须编译为对引用的限定类型的擦除(§4.6)的符号引用,以及字段的简单名称f
。
换句话说,表达式f
将使用NewbornBaby.hoursOfSleep
作为限定类型进行编译,并且运行时必须像编译器一样再次在超类型中查找实际字段。如果运行时存在NewbornBaby
的另一个版本,并且具有该名称和类型的匹配字段,那么将使用该字段。
无法在运行时加载类NewbornBaby
来确定哪种情况适用。
此外,将在记录类加载时超出规格。看起来,触发加载时并不会发生,而是加载完成时。这确实已经包括一些验证步骤,包括加载和检查超类是否存在以及是否兼容(即不是NewbornBaby
,不是interface
等)。
因此,当验证者遇到对类final
的访问时,它将触发该类的加载,从而触发NewbornBaby
的加载。但是NewParent
的加载首先完成并首先报告,因为完成NewParent
的加载对于完成之后记录的日志是必需的。
但是,如上所述,这是特定于实现的。仅精确指定了初始化。
,在这种情况下,如您在书中引用的那样,JVM首先加载NewbornBaby
并发现NewbornBaby
没有hoursOfSleep
字段,然后它继续加载{{1} }的超类NewbornBaby
”:JVM尝试加载NewParent
,但是该类是NewbornBaby
的子类,因此要加载它,需要加载其超类的所有类方法,因此加载NewParent
(第一个)是加载newbornBaby
(第二个)的必要条件。
在继续加载NewParent
的过程中,看起来它像是先加载的,并且确实加载了,但是请记住,这是加载newbornBaby
(已启动的过程)的子过程