第一篇 JVM之類載入過程
在Java虛擬機器規範中,把描述類的資料從class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的java.lang.Class物件,這個過程被稱作類載入過程。一個類在整個虛擬機器週期內會經歷如下圖的階段,從載入到初始化就是類載入過程。
一、載入階段
這裡的載入是整個類載入過程中的一個階段,不等同於類載入,在載入階段,會做以下三件事:
- 1、通過類的全限定名讀取類的二進位制流。
- 2、將位元組流所代表的的靜態儲存結構轉化為方法區的執行時資料結構。
- 3、在記憶體中生成一個代表這個類的java.lang.Class物件,用於方法區這個類的各種資料的訪問入口(如下圖所示)。
由於Java虛擬機器對載入class檔案的來源並未做限制,所以出現了以下的class檔案載入方式:
- 1、從本地系統中直接獲取
- 2、從網路中獲取,如:Web Applet
- 3、從zip壓縮包中獲取,將zip壓縮字尾改為.jar,也可以直接使用
- 4、動態代理生成
- 5、由其他檔案生成,如JSP
- 6、從資料庫中獲取
- 7、加密檔案中獲取,如Class檔案加密防反編譯
二、連結階段
在載入階段完成之後,class檔案的類資訊資料就會儲存在方法區,同時在Java虛擬機器堆區生成一個對應類的Class物件,這個Class物件會在之後變成程式訪問方法區中的型別資料的外部介面。連結階段並不是一定等到載入階段完成後才開始,連結的部分動作會跟隨載入階段進行(如部分位元組碼檔案格式的驗證動作)。
1、驗證
驗證是連結的第一個階段,這個過程中,JVM會去校驗class檔案格式及class檔案二進位制流中所包含的資訊是不是符合虛擬機器規範的約束。包含四部分內容的驗證:
- 檔案格式驗證:驗證class檔案魔數值是否為0xCAFEBABE、主次版本號、常量型別等。
- 元資料驗證:對類的元資料資訊進行語義校驗。
- 位元組碼驗證:通過資料流分析和控制流分析、確定程式語義是合法的、符合邏輯的。
- 符號引用驗證:驗證發生在解析階段,主要對常量池中的各種符號引用進行匹配性校驗。
2、準備
在準備階段,會給類變數(被static修飾的靜態變數)分配記憶體並且初始化類變數初值(零值),如上表就是各種型別對應的零值,從概念上講,這些變數所使用的記憶體都應當在方法區中進行分配,但是方法區
注意:
- 1、final修飾的類變數(常量)並不會進行準備階段進行賦初值的操作,在編譯的時候會給屬性新增ConstantValue屬性,準備階段直接完成賦值。
- 2、正因為類變數擁有賦初值這一操作,所以只宣告類變數,不進行賦值動作,程式也能正常執行,如下程式碼可以驗證各種型別的初值。
public class ClassLoaderPrepare { public static int i; public static void main(String[] args) { System.out.println(i); } }
3、解析
解析階段的作用是將符號引用轉為直接引用。每個class檔案都對應一個常量池,常量池中儲存了類、介面、欄位、方法等各類資訊,符號引用是一組符號指向常量池中被引用的目標,要在虛擬機器中定位到目標,就需要指向對應目標的記憶體地址,這種引用就是直接引用。
三、初始化階段
在連結階段的準備階段中,已經為類變數分配了記憶體地址和初值,在初始化階段就會對這些類變數進行賦值操作。如果一個類含有靜態變數或者靜態程式碼塊,java虛擬機器就會在編譯為其生成一個<clinit>方法(類初始化方法),其內容由編譯期間虛擬機器收集到的類賦值動作和靜態程式碼塊合併而來。
注意:
- 1、<clinit>方法中,指令的順序是依據指令對應的語句在原始檔中出現的順序,靜態程式碼塊中只能訪問定義在它之前的變數,如下程式碼就會提示非法的前向引用。
- 2、在繼承關係中,父類的<clinit>方法先於子類執行。
- 3、在多執行緒同時初始化一個類時,只有其中一個執行緒能夠執行<clinit>
public class ClassLoaderCLInit { static { i = 10; System.out.println(i); } public static int i; }