跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • 关于“Inside the Java Virtual Machine – Chapter 7 The Lifetime of a Type – Initialization”的疑问
未分類
7 9 月 2020

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

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

資深大佬 : JasonLaw 0

在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 呢?

大佬有話說 (22)

  • 資深大佬 : ik2h

    就算从不创建类的实例,类字段也和类关联,类字段会在调用构造方法前初始化,javac 会为每个类自动生成一个类初始化方法,类字段会在这个方法里初始化,类的初始化是内部方法,对程序员不可见.

    如果我没记错,可以用 javap 看看初始化方法, 你给的这个例子全是 static,直接看深入 static 方面的知识就好了

  • 主 資深大佬 : JasonLaw

    @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

    @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

    load 子类肯定要先 load 父类吧

  • 主 資深大佬 : JasonLaw

    @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

    @JasonLaw 编译时常量

  • 資深大佬 : ik2h

    @JasonLaw 这方面是关于类的主动引用和被动引用, 我记得 final static 在 java 编程思想里面也有提及这方面的内容

  • 主 資深大佬 : JasonLaw

    @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

    @Jooooooooo #4 你可以看看第一条附言。既然子类没有先被 loaded,JVM 是怎么知道子类的父类是什么呢?

  • 資深大佬 : ik2h

    @JasonLaw 《深入理解 Java 虚拟机》 7.2 有比较详细的解释,编译时常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。

  • 資深大佬 : Jooooooooo

    @JasonLaw 可以打开 .class 文件看一下 main 方法的字节码是怎么样的. 特别是这一句 int hours = NewbornBaby.hoursOfSleep; 引用的常量池是怎么对应的

  • 主 資深大佬 : JasonLaw

    @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

    @ik2h #10 我感觉,我们根本不是在讨论一个东西,相关的回复也没有什么上下文关系。

  • 資深大佬 : mind3x

    @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

    @mind3x #14

    typo: “继续加载 NewbornBaby -> 加载 NewParent 结束” 应为 “继续加载 NewbornBaby -> 加载 NewbornBaby” 结束。

  • 主 資深大佬 : JasonLaw

    @Jooooooooo #4 子类还是要先被 loaded 的,只是父类先被 loaded 完成,具体可以看看第 2 条附言。

  • 主 資深大佬 : JasonLaw

    @mind3x #14
    @mind3x #15

    “为什么 NewParent 先于 NewbornBaby 被 loaded”是我自己没有认真看清楚的问题,我的核心问题并不是这个,我的核心问题是“为什么 NewbornBaby need not be loaded”。关于这个问题,Holger 在 Stack Overflow 上面回答我了,具体可以看看第 2 条附言。

  • 主 資深大佬 : JasonLaw

    @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

    @JasonLaw 你最早看的那篇是文章是错的蛮坑的.

  • 主 資深大佬 : JasonLaw

    @Jooooooooo #19 其实 Inside the Java Virtual Machine 还是很好的,现在看了 Chapter 5-7,只发现了这一个错误,其他的都解释得很好。

  • 資深大佬 : mind3x

    @JasonLaw

    #17 “NewbornBaby need not be loaded” 这句话是错的,很多书都有错,这个正常。以 JLS, JVMS 为准。

    #18 没错,”A reference to a static field (§8.3.1.1) causes initialization of only the class or interface that actually declares it” 是正确的。这里的重点是 “reference” (敲黑板)。你加了 final,这个值就是个常量,直接在编译时放进 Example2 的常量池了,而不是引用 NewbornBaby 。你可以看字节码的区别。

  • 主 資深大佬 : JasonLaw

    @mind3x 如果对“a reference to a static field”是这样理解的话,那句话的确是对的。我的关注点是 source code,而你的是 compiled code 。总之谢谢你的回复。

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具