java - 为什么“Java 虚拟机内部”说“不需要加载新生儿宝宝”?
问题描述
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 被初始化。NewbornBaby 未初始化,无需加载。
Example2
参考文献NewbornBaby
,我认为应该是“JVMNewbornBaby
首先加载,发现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
.
解决方案
您遇到了类加载和初始化的常见混淆。
您链接的文章描述了由一些定义明确的操作触发的初始化:
§12.4.1。初始化发生时
类或接口类型 T 将在以下任何一项第一次出现之前立即初始化:
T
是一个类,并T
创建了一个实例。- 调用
static
由声明的方法。T
- 分配
static
了声明的字段。T
- 使用
static
声明的字段,T
并且该字段不是常量变量(§4.12.4)。
Your code is accessing a static
field in class NewParent
which will trigger the initialization of that class. The way you access it, is irrelevant. So when you run your code without logging, it printed
Example2 was initialized.
NewParent was initialized.
1
So NewbornBaby
has not been initialized, as none of the specified trigger actions were performed.
Class loading, however, is an entirely different thing. Its timing is intentionally unspecified, except that it must happen before initialization. A JVM may eagerly load all referenced classes, even before the application starts, or defer the loading, until either, the verifier or the application needs it.
At this point, it's important to understand that while the compiler will check whether the referenced static
field exists and will find it in the class NewParent
, it will produce bytecode still using the type that has been used in the source code. So, loading the specified class NewbornBaby
at runtime is unavoidable (the article is wrong in this regard), even if it won't get initialized (which the article seems to confuse with loading).
Compare with 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
.
In other words, the expression NewbornBaby.hoursOfSleep
will get compiled using NewbornBaby
as the qualifying type and the runtime has to find the actual field again in the supertype, like the compiler did. If there was a different version of NewbornBaby
at runtime having a matching field of that name and type, that field was used instead.
There is no way around loading the class NewbornBaby
at runtime, to find out which scenario applies.
Further, it's off-specification when the class loading will be logged. It seems, it does not happen when the loading is triggered, but when the loading completed. This does already include some verification steps, including loading and checking whether the superclass exists and is compatible (i.e. not an interface
, not final
, etc.).
So when the verifier encounters an access to class NewbornBaby
, it triggers the loading of that class, which triggers the loading of NewParent
. But the loading of NewParent
completes first and is reported first, as its completion is necessary to complete the loading of NewbornBaby
which is logged afterwards.
But, as said, that's implementation specific. Only the initialization is precisely specified.
推荐阅读
- python - 如何正确添加对象之间逐渐增加/减少的空间?
- apache-nifi - Nifi:使用出处数据审计数据
- html - 用文字创建一个圆形图像
- python - 数据框列名称未定义 PowerBI Python 集成
- java - java中的自动化J单元软件测试
- excel - VBA将范围复制到最后一行并粘贴到新工作表上,单元格A19之后的第一个空行
- opencv - 是否有任何 opencv android 函数可用于在没有 for 循环的情况下替换像素值?
- xamarin - AppCenter 崩溃和 Firebase Crashlytics - 两者都不起作用 - Xamarin.Forms iOS
- azure - ARM 模板部署返回一个我不期望的值
- powershell - 如何通过 Powershell 找到身份验证域控制器