为什么 "Inside the Java Virtual Machine" 说 "NewbornBaby need not be loaded"?
Why does "Inside the Java Virtual Machine" say "NewbornBaby need not be loaded"?
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
。
在这种情况下,正如您在书中引用的那样,JVM 首先加载 NewbornBaby
,它发现 NewbornBaby
没有 hoursOfSleep
字段,然后它继续加载 NewbornBaby
的 superclass NewParent
":JVM 尝试加载 NewbornBaby
但此 class 是 NewParent
的子 class所以要加载它需要加载它的 superclass 的所有 class 方法,所以要加载 newbornBaby
(first) 需要加载 NewParent
(second) 作为正在加载 newbornBaby
.
在恢复 NewParent
的加载时,看起来它首先加载,但它确实加载了,但请记住这是加载 newbornBaby
(已经启动的进程)[=22= 的子进程]
您 运行 陷入了 class 加载 和 初始化 .
的常见混淆
您链接的文章描述了初始化,这是由某些 well defined actions:
触发的
§12.4.1. When Initialization Occurs
A class or interface type T will be initialized immediately before the first occurrence of any one of the following:
T
is a class and an instance of T
is created.
- A
static
method declared by T
is invoked.
- A
static
field declared by T
is assigned.
- A
static
field declared by T
is used and the field is not a constant variable (§4.12.4).
您的代码正在访问 class NewParent
中的 static
字段,这将触发 class 的初始化。您访问它的方式无关紧要。所以当你 运行 你的代码没有记录时,它会打印
Example2 was initialized.
NewParent was initialized.
1
因此 NewbornBaby
尚未初始化,因为执行了 none 个指定的触发操作。
Class loading 然而,是完全不同的事情。它的时间是有意未指定的,除了它必须发生在初始化之前。 JVM 可能急切地加载所有引用的 classes,甚至在应用程序启动之前,或者延迟加载,直到验证器或应用程序需要它。
此时,重要的是要了解编译器将检查引用的 static
字段是否存在并在 class NewParent
中找到它,它会生成字节码仍然使用源代码中使用的类型。所以,在运行时间加载指定的classNewbornBaby
是不可避免的(文章在这方面是错误的),即使它不会被初始化(文章似乎与加载中)。
与JLS, §13.1. The Form of a Binary比较:
Given a legal expression denoting a field access in a class C
, referencing a field named f
that is not a constant variable and is declared in a (possibly distinct) class or interface D
, we define the qualifying type of the field reference as follows:
...
- If the reference is of the form TypeName
.f
, where TypeName denotes a class or interface, then the class or interface denoted by TypeName is the qualifying type of the reference.
...
The reference to f
must be compiled into a symbolic reference to the erasure (§4.6) of the qualifying type of the reference, plus the simple name of the field, f
.
换句话说,表达式 NewbornBaby.hoursOfSleep
将使用 NewbornBaby
作为限定类型进行编译,并且 运行 时间必须在超类型中再次找到实际字段,例如编译器做到了。如果在 运行 时 NewbornBaby
的不同版本具有该名称和类型的匹配字段,则使用该字段。
无法在 运行 时间加载 class NewbornBaby
,以找出适用的场景。
此外,off-specification 将记录 class 加载。看来,它不会在触发加载时发生,而是在加载完成时发生。这确实已经包括了一些验证步骤,包括加载和检查 superclass 是否存在且兼容(即不是 interface
、不是 final
等)。
因此,当验证者遇到对 class NewbornBaby
的访问时,它会触发 class 的加载,从而触发 NewParent
的加载。但是 NewParent
的加载首先完成并首先报告,因为它的完成对于完成随后记录的 NewbornBaby
的加载是必要的。
但是,如前所述,这是特定于实现的。只有初始化被精确指定。
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
。
在这种情况下,正如您在书中引用的那样,JVM 首先加载 NewbornBaby
,它发现 NewbornBaby
没有 hoursOfSleep
字段,然后它继续加载 NewbornBaby
的 superclass NewParent
":JVM 尝试加载 NewbornBaby
但此 class 是 NewParent
的子 class所以要加载它需要加载它的 superclass 的所有 class 方法,所以要加载 newbornBaby
(first) 需要加载 NewParent
(second) 作为正在加载 newbornBaby
.
在恢复 NewParent
的加载时,看起来它首先加载,但它确实加载了,但请记住这是加载 newbornBaby
(已经启动的进程)[=22= 的子进程]
您 运行 陷入了 class 加载 和 初始化 .
的常见混淆您链接的文章描述了初始化,这是由某些 well defined actions:
触发的§12.4.1. When Initialization Occurs
A class or interface type T will be initialized immediately before the first occurrence of any one of the following:
T
is a class and an instance ofT
is created.- A
static
method declared byT
is invoked.- A
static
field declared byT
is assigned.- A
static
field declared byT
is used and the field is not a constant variable (§4.12.4).
您的代码正在访问 class NewParent
中的 static
字段,这将触发 class 的初始化。您访问它的方式无关紧要。所以当你 运行 你的代码没有记录时,它会打印
Example2 was initialized.
NewParent was initialized.
1
因此 NewbornBaby
尚未初始化,因为执行了 none 个指定的触发操作。
Class loading 然而,是完全不同的事情。它的时间是有意未指定的,除了它必须发生在初始化之前。 JVM 可能急切地加载所有引用的 classes,甚至在应用程序启动之前,或者延迟加载,直到验证器或应用程序需要它。
此时,重要的是要了解编译器将检查引用的 static
字段是否存在并在 class NewParent
中找到它,它会生成字节码仍然使用源代码中使用的类型。所以,在运行时间加载指定的classNewbornBaby
是不可避免的(文章在这方面是错误的),即使它不会被初始化(文章似乎与加载中)。
与JLS, §13.1. The Form of a Binary比较:
Given a legal expression denoting a field access in a class
C
, referencing a field namedf
that is not a constant variable and is declared in a (possibly distinct) class or interfaceD
, we define the qualifying type of the field reference as follows:...
- If the reference is of the form TypeName
.f
, where TypeName denotes a class or interface, then the class or interface denoted by TypeName is the qualifying type of the reference....
The reference to
f
must be compiled into a symbolic reference to the erasure (§4.6) of the qualifying type of the reference, plus the simple name of the field,f
.
换句话说,表达式 NewbornBaby.hoursOfSleep
将使用 NewbornBaby
作为限定类型进行编译,并且 运行 时间必须在超类型中再次找到实际字段,例如编译器做到了。如果在 运行 时 NewbornBaby
的不同版本具有该名称和类型的匹配字段,则使用该字段。
无法在 运行 时间加载 class NewbornBaby
,以找出适用的场景。
此外,off-specification 将记录 class 加载。看来,它不会在触发加载时发生,而是在加载完成时发生。这确实已经包括了一些验证步骤,包括加载和检查 superclass 是否存在且兼容(即不是 interface
、不是 final
等)。
因此,当验证者遇到对 class NewbornBaby
的访问时,它会触发 class 的加载,从而触发 NewParent
的加载。但是 NewParent
的加载首先完成并首先报告,因为它的完成对于完成随后记录的 NewbornBaby
的加载是必要的。
但是,如前所述,这是特定于实现的。只有初始化被精确指定。