對類加載時機中初始化(Initialization)時機的理解

1. 類的生命週期過程

從類被加載到JVM內存中開始到被卸載出內存爲止,生命週期會經過以下7過程:
在這裏插入圖片描述

  • 加載、驗證、準備、初始化和卸載這五個階段的順序是確定的。
  • 解析階段不一定按上圖順序進行,在某些情況下解析階段可以在初始化之後進行(爲了支持Java的運行時綁定特性,也稱爲動態綁定)

2. 六種必須立即進行初始化的操作

”加載“過程什麼時候開始並沒有明確的規定,但是什麼時候必須進行類初始化有着嚴格的規定,僅在以下6種情況中需要立即進行類初始化操作(初始化之前的必要操作加載、驗證、準備肯定也要進行)
這6種場景稱爲對一個類型進行“主動引用”,除此之外所有引用類型方式都不會觸發初始化

  1. 遇到new、getstatic、putstatic或者invokestatic這四條字節碼指令時,如果類型沒有進行過初始化,則需要先觸發其初始化階段。能夠生成這四條指令的典型Java代碼場景有:
  • 使用new關鍵字實例化對象的時候
  • 讀取或者設置一個類型的靜態字段時(被final修飾,已在編譯期將結果放入常量池的靜態字段除外,注意是除外除外除外!)
  • 調用一個類型的靜態方法的時候
  1. 使用java.lang.reflect包的方法對類型進行反射調用的時候,如果沒有進行過初始化,則需要先進行初始化
  2. 當初始化類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化
  3. 當JVM啓動的時候,用戶需要指定一個要執行的主類(包含main方法的類)虛擬機會先初始化這個類
  4. 當使用JDK7新加入的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果爲REF_getstatic、REF_putstatic、REF_invokeStatic、REF_newInvokeSpecial四種類型的方法句柄,並且這個方法句柄對應的類沒有進行過初始化,則需要先觸發其初始化
  5. 當一個接口中定義了JDK8新加入的默認方法(被default關鍵字修飾的接口方法)時,如果有這個接口的實現類發生了初始化,則該接口要在其之前被初始化

3. 不會觸發 類初始化 的情況

以上的6種場景的行爲成爲對一個類型進行主動引用,除此之外的所有引用類型都不會觸發初始化,稱爲被動引用。

列舉三種不會觸發類初始化的場景:

  • Demo1: 通過子類引用父類的靜態字段,不會導致子類初始化
class A{
    public static String super_str = "super_str";

    static {
        System.out.println("A初始化...");
    }
    A(){
        System.out.println("A被創建...");
    }
}

class B extends A{
    public static String child_str = "child_str";

    static {
        System.out.println("B初始化...");
    }
    B(){
        System.out.println("B被創建...");
    }
}

public class Demo1 {
    public static void main(String[] args) {
        // 通過子類引用父類中的靜態字段
        System.out.println(B.super_str);
    }
}

輸出結果及其分析:

A初始化...
super_str

分析:對於靜態字段,只有直接定義這個字段的類纔會被初始化,
因此通過其子類來引用父類中定義的靜態字只會觸發父類的初始化

除此之外:是否要觸發子類的加載和驗證階段:
在HotSpot VM通過-XX:+TraceClassLoading參數觀察到此操作是 會觸發的
  • Demo2:
class A{
    static {
        System.out.println("A初始化...");
    }
}
public class Demo2 {
    public static void main(String[] args) {
        // 使用數組定義來引用類,不會觸發類的初始化
        A[] aa = new A[10];
    }
}

輸出結果及其分析:

什麼也沒輸出...

分析:沒有任何輸出說明沒有觸發A類的初始化

(以下內容暫未實驗,暫時轉述書本上的內容)

但是會觸發一個名爲"[Lorg.feinxsoft.classloading.A"的類初始化階段,對於用戶代碼來說,
這不是一個合法的類型名稱,它是一個由JVM自動生成的,直接繼承與java.lang.Object的子類,
創建動作由字節碼指令newarray觸發。

這個類代表了一個元素類型爲org.feinxsoft.classloading.A的一維數組,
數組中應有的屬性和方法都被實現在這個類裏面。
  • Demo3:
class A{

	// 和Demo1相比僅僅多了final
    public static final String super_str = "super_str";

    static {
        System.out.println("A初始化...");
    }
    A(){
        System.out.println("A被創建...");
    }
}
public class Demo3 {
    public static void main(String[] args) {
        // // 使用類中的final常量
        System.out.println(A.super_str);
    }
}

輸出結果及其分析:

super_str

分析:常量在編譯階段會被存入調用類的常量值中,本質上沒有直接引用到定義常量的類,
因此不會觸發定義常量的類的初始化。

在編譯階段通過常量傳播優化,已經將常量的值"super_str"直接存儲在Demo3的類的常量池中,
以後Demo3中對常量super_str的引用實際上都被轉化爲Demo3類對自身常量池的引用了。

實際上Demo3的Class文件中並沒有A類的符號引用入口,
這兩個類在編譯成Class文件之後就沒有任何聯繫了

4. 有關接口

接口也有初始化過程,在前面的6中必須進行初始化的操作中,有一點不同:

  • 當一個類在初始化的時候,要求其父類全部已經初始化過了,否則先對父類進行初始化
  • 當一個接口在進行初始化的時候,不要求其父類接口全部完成了初始化,只有在真正使用到父接口的時候纔會初始化,比如要使用父接口中定義的常量的時候。

參考:
https://www.jianshu.com/p/7ecbb90478ef
https://blog.csdn.net/weixin_34211761/article/details/88030782

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章