在Java虛擬機規范中,把描述類的數據從class文件加載到內存,并對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的java.lang.Class對象,這個過程被稱作類加載過程。一個類在整個虛擬機周期內會經歷如下圖的階段,從加載到初始化就是類加載過程。
加載階段
這里的加載是整個類加載過程中的一個階段,不等同于類加載,在加載階段,會做以下三件事:
- 通過類的全限定名讀取類的二進制流。
- 將字節流所代表的的靜態存儲結構轉化為方法區的運行時數據結構。
- 在虛擬機內存的堆區中生成一個代表這個類的java.lang.Class對象,用于方法區這個類的各種數據的訪問入口(如下圖所示)。
由于Java虛擬機對加載class文件的來源并未做限制,所以出現了以下的class文件加載方式: - 從本地系統中直接獲取
- 從網絡中獲取,如:Web Applet
- 從zip壓縮包中獲取,將zip壓縮后綴改為.jar,也可以直接使用
- 動態代理生成
- 由其他文件生成,如JSP
- 從數據庫中獲取
- 加密文件中獲取,如Class文件加密防反編譯
鏈接階段
在加載階段完成之后,class文件的類信息數據就會存儲在方法區,同時在Java虛擬機堆區生成一個對應類的Class對象,這個Class對象會在之后變成程序訪問方法區中的類數據的外部接口。鏈接階段并不是一定等到加載階段完成后才開始,鏈接的部分動作會跟隨加載階段進行(如部分字節碼文件格式的驗證動作)。
- 驗證
驗證是鏈接的第一個階段,這個過程中,JVM會去校驗class文件格式及class文件二進制流中所包含的信息是不是符合虛擬機規范的約束。包含四部分內容的驗證:
- 文件格式驗證:驗證class文件魔數值是否為0xCAFEBABE、主次版本號、常量類型等。
- 元數據驗證:對類的元數據信息進行語義校驗。
- 字節碼驗證:通過數據流分析和控制流分析、確定程序語義是合法的、符合邏輯的。
- 符號引用驗證:驗證發生在解析階段,主要對常量池中的各種符號引用進行匹配性校驗。
- 準備
在準備階段,會給類變量(被static修飾的靜態變量)分配內存并且初始化類變量初值(零值),如上表就是各種類型對應的零值,從概念上講,這些變量所使用的內存都應當在方法區中進行分配,但是方法區本身是一個邏輯上的區域,在JDK7及之前,HotSpot使用永久代來實現方法區時,實現是完全符合這種邏輯概念的;而在JDK8及之后,類變量則會隨著Class對象一起存放在Java堆中,這時候"類變量在方法區" 就完全是一種對邏輯概念的表述;
注意: - final修飾的類變量(常量)并不會進行準備階段進行賦初值的操作,在編譯的時候會給屬性添加ConstantValue屬性,準備階段直接完成賦值。
- 正因為類變量擁有賦初值這一操作,所以只聲明類變量,不進行賦值動作,程序也能正常執行,如下代碼可以驗證各種類型的初值。
public class ClassLoaderPrepare {public static int i;public static void main(String[] args) {System.out.println(i);}
}
- 解析
解析階段的作用是將符號引用轉為直接引用。每個class文件都對應一個常量池,常量池中存儲了類、接口、字段、方法等各類信息,符號引用是一組符號指向常量池中被引用的目標,要在虛擬機中定位到目標,就需要指向對應目標的內存地址,這種引用就是直接引用。
初始化階段
在鏈接階段的準備階段中,已經為類變量分配了內存地址和初值,在初始化階段就會對這些類變量進行賦值操作。如果一個類含有靜態變量或者靜態代碼塊,java虛擬機就會在編譯為其生成一個方法(類初始化方法),其內容由編譯期間虛擬機收集到的類變量的賦值動作和靜態代碼塊合并而來。
注意:
- 方法中,指令的順序是依據指令對應的語句在源文件中出現的順序,靜態代碼塊中只能訪問定義在它之前的變量,如下代碼就會提示非法的前向引用。
- 在繼承關系中,父類的方法先于子類執行。
- 在多線程同時初始化一個類時,只有其中一個線程能夠執行
public class ClassLoaderCLInit {static {i = 10;System.out.println(i);}public static int i;
}