Java基礎——深入理解類的載入

it_was發表於2020-09-18

Java虛擬機器將描述類的資料從.Class檔案裝入虛擬機器記憶體,並對資料進行驗證,準備,解析和初始化,最終形成Java虛擬機器可以直接使用的Java型別的過程為類的載入

執行期間。
不是編譯期間哦,類的所有工作都是在程式執行期間完成的,雖然這給Java虛擬機器編譯帶來額外的困難,但是這種動態載入和動態連結卻實現了Java的動態擴充套件這一語言特性

3.1 :boom:最重要的一點 —— 類的生命週期:boom:

:star:載入(loading)
:star:連結(Linking)

  • :small_orange_diamond:驗證(Verification)
  • :small_orange_diamond:準備(Preparation)
  • :small_orange_diamond:解析(Resolution)

:star:使用(Using)
:star:解除安裝(Unloading)

載入,驗證,準備,初始化和解除安裝這五個階段的開始順序是確定的,而解析則不一定,在某些情況下會在類的初始化階段之後開始!!

3.2 類初始化的六大時機:clock2:

載入,驗證和準備已經開始
:one:遇到new,getstatic,putstatic或invokestatic這四條位元組碼指令

  • 對於第一個new指令毋庸置疑,新建立例項必定會涉及類初始化
  • 對於二三指令就是獲取或者設定一個靜態欄位
  • 對於第四條指令就是呼叫靜態方法

:two:當使用java.lang.reflect包的方法進行反射呼叫時會進行相應的類初始化
:three:當初始化類的時候,如果發現父類沒有被初始化則先初始化父類!
:four:當Java虛擬機器啟動時,會首先進行main主類進行初始化
剩下兩種可以去了解
:boom: 注意,Java虛擬機器規範規定,有且只有以上六種情況才會進行類的初始化,這六種情況被稱為對一個型別的主動引用

:raised_hand:既然有主動引用,就有對應的被動引用,舉例說明

/**
情況一:通過子類引用父類的靜態欄位不會觸發子類的初始化!!!
**/
public class Main {
    public static void main(String[] args){
        System.out.println(subclass.age);
    }
}
class parent{
    public static int age  = 0; //靜態欄位
    static {
        System.out.println("parent is loading!");
    }
}
class subclass extends  parent{
    static {
        System.out.println("subclass is loading!");
    }
}
/**
情況二:通過陣列定義來引用類,不會觸發類的初始化!!!
**/
public class Main {
    public static void main(String[] args){
        parent[] parents = new parent[10];
        System.out.println(subclass.age);
    }
}
class parent{
    public static int age  = 0; //靜態欄位
    static {
        System.out.println("parent is loading!");
    }
}
/**
情況三:常量!!!
**/
public class Main {
    public static void main(String[] args){
        parent[] parents = new parent[10];
        System.out.println(subclass.age);
    }
}
public class parent{
    public final static int age  = 0; //常量欄位
    static {
        System.out.println("parent is loading!");
    }
}
解釋一下:上述程式碼並沒有列印 parent is loading! 原因在於,常量在編譯階段通過傳播優化,已經將此常量的值存入在Main類的常量池種,實際上對parent類中常量的引用都轉為對Main中常量的引用!!!

:facepunch:以上就是類的初始化時機和一些儘管看起來覺得需要進行初始化實際上並沒有進行初始化的情況!!!

:one:載入

  • 通過一個類的全限定名來獲取定義此類的二進位制位元組流
  • 將這個位元組流中蘇代表的靜態儲存結構轉換為方法去的執行時資料集
  • 在記憶體中生成一個代表這個類的java.lang.Class物件(僅此一份)!作為方法去這個類的各種資料的訪問入口
    注意:陣列的載入與以上步驟不同,但最終降維還是要進行類的載入過程的!

:two:驗證

  • 檔案格式驗證
  • 後設資料驗證
  • 位元組碼驗證
  • 符號引用驗證
    詳細過程自己看書!

:three:準備

準備階段主要是正式為類中定義的變數(即靜態變數,static修飾的變數)分配記憶體並設定類變數初始值的階段!注意此時並不分配例項變數!例項變數會隨著物件例項化一起分配在Java堆中!

public static int age = 19;
注意,類載入中的準備階段並不會立馬賦值為19,而是先賦初始值為0!!!!!直到進行類載入的初始化階段才會進行真正的賦值!!!

:four:解析

解析階段就是Java虛擬機器將常量池內的符號引用替換為直接飲用的過程!!

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章