关于“Inside the Java Virtual Machine - Chapter 7 The Lifetime of a Type - Initialization”的疑问

2020-09-05 11:22:03 +08:00
 JasonLaw

Inside the Java Virtual Machine - Chapter 7 The Lifetime of a Type - Initialization中,有这么一段:

A use of a non-constant static field is an active use of only the class or interface that actually declares the field. For example, a field declared in a class may be referred to via a subclass. A field declared in an interface may be referred to via a subinterface or class that implements the interface. These are passive uses of the subclass, subinterface, or class that implements the interface--uses that won't trigger their initialization. They are an active use only of the class or interface in which the field is actually declared. Here's an example that illustrates this principle:

// On CD-ROM in file classlife/ex2/NewParent.java
class NewParent {

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

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

// On CD-ROM in file classlife/ex2/NewbornBaby.java
class NewbornBaby extends NewParent {

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

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

// On CD-ROM in file classlife/ex2/Example2.java
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.");
    }
}

然后它说:

In the above example, executing main() of Example2 causes only Example2 and NewParent to be initialized. NewbornBaby is not initialized and need not be loaded.


我明白“NewbornBaby is not initialized”,但是为什么“NewbornBaby need not be loaded”呢?如果 NewbornBaby 都没有被 loaded,那么 JVM 怎么可能知道 hoursOfSleep 是来源于 NewParent 的呢?

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

虽然结果显示 NewbornBaby 被 loaded 了,但是为什么 NewParent 先于 NewbornBaby 被 loaded 呢?

1925 次点击
所在节点    Java
22 条回复
ik2h
2020-09-05 11:44:04 +08:00
就算从不创建类的实例,类字段也和类关联,类字段会在调用构造方法前初始化,javac 会为每个类自动生成一个类初始化方法,类字段会在这个方法里初始化,类的初始化是内部方法,对程序员不可见.

如果我没记错,可以用 javap 看看初始化方法, 你给的这个例子全是 static,直接看深入 static 方面的知识就好了
JasonLaw
2020-09-05 12:25:35 +08:00
@ik2h #1 你说的都是关于 initialization 的,而我的问题是关于 loading 的。

顺便说一下,“javac 会为每个类自动生成一个类初始化方法”不是完全正确的。

关于什么情况会产生()方法,https://www.artima.com/insidejvm/ed2/lifetype4.html 中描述了很清楚,以下是一些片段:

Not all classes will necessarily have a () method in their class file. If a class declares no class variables or static initializers, it won't have a () method. If a class declares class variables, but doesn't explicitly initialize them with class variable initializers or static initializers, it won't have a () method. If a class contains only class variable initializers for static final variables, and those class variable initializers use compile-time constant expressions, that class won't have a () method. Only those classes that actually require Java code to be executed to initialize class variables to proper initial values will have a class initialization method.

Interfaces may also be awarded a () method in the class file. All fields declared in an interface are implicitly public, static, and final and must be initialized with a field initializer. If an interface has any field initializers that don't resolve at compile-time to a constant, that interface will have a () method.
ik2h
2020-09-05 12:40:33 +08:00
@JasonLaw https://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html

A reference to a static field (§8.3.1.1) causes initialization of only the class or interface that actually declares it, even though it might be referred to through the name of a subclass, a subinterface, or a class that implements an interface.
Jooooooooo
2020-09-05 12:52:22 +08:00
load 子类肯定要先 load 父类吧
JasonLaw
2020-09-05 13:04:08 +08:00
@ik2h #3 先抛开其他的,单纯讨论 3 楼所引用的内容。

“A reference to a static field (§8.3.1.1) causes initialization of only the class or interface that actually declares it”不一定正确。主题正文中的外链(因为提示回复不能包含外链,所以只能这么弄)说了“A use of a field that is both static and final, and initialized by a compile-time constant expression, is not an active use of the type that declares the field.”,其中的 Example3 也演示了(虽然 Example3 中引用了 Angry.greeting 和 Dog.greeting,但是 Angry 和 Dog 都没有被初始化)。

还是我哪里理解错了?
ik2h
2020-09-05 13:35:09 +08:00
@JasonLaw 编译时常量
ik2h
2020-09-05 13:42:22 +08:00
@JasonLaw 这方面是关于类的主动引用和被动引用, 我记得 final static 在 java 编程思想里面也有提及这方面的内容
JasonLaw
2020-09-05 13:43:52 +08:00
@ik2h #6 是的,所以“A reference to a static field (§8.3.1.1) causes initialization of only the class or interface that actually declares it”不一定正确呀。难道“a field that is both static and final, and initialized by a compile-time constant expression”不是一个“static field”?
JasonLaw
2020-09-05 13:44:58 +08:00
@Jooooooooo #4 你可以看看第一条附言。既然子类没有先被 loaded,JVM 是怎么知道子类的父类是什么呢?
ik2h
2020-09-05 14:05:29 +08:00
@JasonLaw 《深入理解 Java 虚拟机》 7.2 有比较详细的解释,编译时常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
Jooooooooo
2020-09-05 14:09:22 +08:00
@JasonLaw 可以打开 .class 文件看一下 main 方法的字节码是怎么样的. 特别是这一句 int hours = NewbornBaby.hoursOfSleep; 引用的常量池是怎么对应的
JasonLaw
2020-09-05 15:11:28 +08:00
@Jooooooooo #11 "int hours = NewbornBaby.hoursOfSleep;"对应的字节码为"getstatic #2 和 istore_1"。在 constant pool 中,index 为 2 的 entry 是一个 CONSTANT_Fieldref_info,通过 CONSTANT_Fieldref_info 中的 class_index 最后会得到 NewbornBaby,通过 CONSTANT_Fieldref_info 中的 name_and_type_index,最后会得到一个 CONSTANT_NameAndType_info,通过 CONSTANT_NameAndType_info 中的 name_index 最后会得到 hoursOfSleep,通过 CONSTANT_NameAndType_info 中的 descriptor_index 最后会得到 I 。

然后呢?为什么父类先于子类被 loaded ?子类没有先被 loaded,JVM 是怎么知道子类的父类是什么呢?
JasonLaw
2020-09-05 15:14:55 +08:00
@ik2h #10 我感觉,我们根本不是在讨论一个东西,相关的回复也没有什么上下文关系。
mind3x
2020-09-07 04:49:13 +08:00
@JasonLaw
> “A reference to a static field (§8.3.1.1) causes initialization of only the class or interface that actually declares it”不一定正确。

@ik2h 在 #3 中引用的是 JLS,虽然版本旧了点,好歹也是正宗官方的语言 specification,何来的“不一定正确”?你关心的问题本来就应该从 JLS 和 JVM Spec 里面寻找答案。

至于你的核心问题 NewParent 为什么先于 NewbornBaby 被 load,原因很简单:类加载是一边解析一边递归的——这里的顺序是 开始加载 NewbornBaby -> 解析 NewbornBaby -> 发现父类 NewParent -> (递归) 开始加载 NewParent -> ... -> 加载 NewParent 结束 -> 继续加载 NewbornBaby -> 加载 NewParent 结束。你看到的 log 只是打了 load 结束而已。

JVM Spec (1.7 版) 5.3: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.3

> A subtlety here is that recursive class loading to load superclasses is performed as part of resolution (§5.3.5, step 3). Therefore, a ClassNotFoundException that results from a class loader failing to load a superclass must be wrapped in a NoClassDefFoundError.
mind3x
2020-09-07 04:50:40 +08:00
@mind3x #14

typo: "继续加载 NewbornBaby -> 加载 NewParent 结束" 应为 "继续加载 NewbornBaby -> 加载 NewbornBaby" 结束。
JasonLaw
2020-09-07 09:48:05 +08:00
@Jooooooooo #4 子类还是要先被 loaded 的,只是父类先被 loaded 完成,具体可以看看第 2 条附言。
JasonLaw
2020-09-07 09:51:07 +08:00
@mind3x #14
@mind3x #15

“为什么 NewParent 先于 NewbornBaby 被 loaded”是我自己没有认真看清楚的问题,我的核心问题并不是这个,我的核心问题是“为什么 NewbornBaby need not be loaded”。关于这个问题,Holger 在 Stack Overflow 上面回答我了,具体可以看看第 2 条附言。
JasonLaw
2020-09-07 09:58:49 +08:00
@mind3x #14 你说“A reference to a static field (§8.3.1.1) causes initialization of only the class or interface that actually declares it”来源于官方文档,一定是正确的。难道“a field that is both static and final, and initialized by a compile-time constant expression”不是一个“static field”?

比如我将 NewbornBaby 改为下面这样:

```
class NewbornBaby extends NewParent {

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

static final int hoursOfSleep = 6; // a field that is both static and final, and initialized by a compile-time constant expression

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

运行 java -verbose Example2,可以看出 NewbornBaby 被没有被 loaded 。
Jooooooooo
2020-09-07 10:35:53 +08:00
@JasonLaw 你最早看的那篇是文章是错的蛮坑的.
JasonLaw
2020-09-07 10:44:30 +08:00
@Jooooooooo #19 其实 Inside the Java Virtual Machine 还是很好的,现在看了 Chapter 5-7,只发现了这一个错误,其他的都解释得很好。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/704416

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX