1 類的加載機制
類的加載指的是將類的.class檔案中的二進制資料讀入到記憶體中,將其放在運行時資料區的方法區內,然后在堆區創建一個java.lang.Class物件,用來封裝類在方法區內的資料結構,類的加載的最終產品是位于堆區中的Class物件,Class物件封裝了類在方法區內的資料結構,并且向Java程式員提供了訪問方法區內的資料結構的介面,
1.1 類的加載程序

1.2 加載
加載,是整個類加載程序的第一步,如果需要創建類或者介面,就需要現在Java虛擬機方法區創建于虛擬機實作規定相匹配的內部表示,一般來說類的創建是由另一個類或者介面觸發的,它通過自己的運行時常量池參考到了需要創建的類,也可能是由于呼叫了Java核心類別庫中的某些方法,譬如反射等,
一般來說加載分為以下幾步:
(1) 通過一個類的全限定名獲取此類的二進制位元組流
(2) 將這個位元組流所代表的靜態存盤結構轉化為方法區的運行時資料結構
(3) 在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料的訪問入口
1.3 驗證
驗證作為鏈接的第一步,用于確保類或介面的二進制表示結構上是正確的,從而確保位元組流包含的資訊對虛擬機來說是安全的,Java虛擬機規范中關于驗證階段的規則也是在不斷增加的,但大體上會完成下面4個驗證動作,
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-G22ThVwb-1613838385913)(類的加載機制.assets/image-20210221000839222.png)]](https://img.uj5u.com/2021/02/22/226985221254182.png)
(1) 檔案格式驗證:主要驗證位元組流是否符合Class檔案格式規范,并且能被當前版本的虛擬機處理,
(2) 元資料驗證:主要對位元組碼描述的資訊進行語意分析,以保證其提供的資訊符合Java語言規范的要求,
(3) 位元組碼驗證:主要是通過資料流和控制流分析,確定程式語意是合法的、符合邏輯的,在第二階段對元資料資訊中的資料型別做完校驗后,位元組碼驗證將對類的方法體進行校驗分析,保證被校驗類的方法在運行時不會做出危害虛擬機安全的事件,
(4) 符號參考驗證:最后一個階段的校驗發生在虛擬機將符號參考轉化為直接參考的時候,這個轉化動作將在連接的第三階段決議階段發生,符號參考是對類自身以外(常量池中的各種符號參考)的資訊進行匹配校驗,
1.4 準備
準備階段的任務是為類或者介面的靜態欄位分配空間,并且默認初始化這些欄位,這個階段不會執行任何的虛擬機位元組碼指令,在初始化階段才會顯示的初始化這些欄位,所以準備階段不會做這些事情,

1.5 決議
決議階段是把常量池內的符號參考替換成直接參考的程序,
符號參考(Symbolic References):符號參考以一組符號來描述所參考的目標,符號可以是任何形式的字面量,只要可以唯一定位到目標即可,符號參考于記憶體布局無關,所以所參考的物件不一定需要已經加載到記憶體中,各種虛擬機實作的記憶體布局可以不同,但是接受的符號參考必須是一致的,因為符號參考的字面量形式已經明確定義在Class檔案格式中,
直接參考(Direct References):直接參考時直接指向目標的指標、相對偏移量或是一個能間接定位到目標的句柄,直接參考和虛擬機實作的記憶體布局相關,同一個符號參考在不同虛擬機上翻譯出來的直接參考一般不會相同,如果有了直接參考,那么它一定已經存在于記憶體中了,
1.6 初始化
初始化是類加載的最后一步,在前面的階段里,除了加載階段可以通過用戶自定義的類加載器加載,其余部分基本都是由虛擬機主導的,但是到了初始化階段,才開始真正執行用戶撰寫的java代碼了,
在準備階段,變數都被賦予了初始值,但是到了初始化階段,所有變數還要按照用戶撰寫的代碼重新初始化,換一個角度,初始化階段是執行類構造器()方法的程序,()方法是由編譯器自動收集類中的所有類變數的賦值動作和靜態陳述句塊(static陳述句塊)中的陳述句合并生成的,編譯器收集的順序是由陳述句在源檔案中出現的順序決定的,靜態陳述句塊中只能訪問到定義在靜態陳述句塊之前的變數,定義在它之后的變數,在前面的靜態陳述句塊中可以賦值,但是不能訪問,
2 類加載的時機
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-rQbwQq07-1613838385919)(類的加載機制.assets/image-20210221002205578.png)]](https://img.uj5u.com/2021/02/22/226985221254184.png)
2.1 類在使用時才會加載
public class Test01 {
public static void main(String[] args) {
System.out.println(TestB.str);
}
static class TestA {
public static String str = "A str";
static {
System.out.println("A static block");
}
}
static class TestB extends TestA {
static {
System.out.println("B static block");
}
}
}
![[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9bU5a2AD-1613838385926)(類的加載機制.assets/image-20210221002301257.png)]](https://img.uj5u.com/2021/02/22/226985221254185.png)
2.2 加載一個類的子類會加載父類
public class Test02 {
public static void main(String[] args) {
System.out.println(new TestB().str);
}
static class TestA {
static {
System.out.println("A static block");
}
}
static class TestB extends TestA {
public String str = "B str";
static {
System.out.println("B static block");
}
}
}

2.3 new 物件觸發類的加載
public class Test03 {
public static void main(String[] args) {
TestA testA = new TestA();
}
static class TestA {
static {
System.out.println("TestA static block");
}
}
}

2.4 static final修飾的常量屬性會存到常量池中,類不會加載
public class Test04 {
public static void main(String[] args) {
System.out.println(TestA.str);
}
static class TestA {
public static final String str = "A str";
static {
System.out.println("TestA static block");
}
}
}

2.5 反射觸發類加載
public class Test05 {
static {
System.out.println("Test05 static block");
}
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("com.rosh.Test01");
}
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/262190.html
標籤:java
