JVM的類加載機制
一、基本概念
JVM 類加載機制分為五個部分:加載,驗證,準備,解析,初始化,下面我們就分別來看一下這五個過程。
1. 加載
加載是類加載過程中的一個階段,這個階段虛擬機要完成3件事。
- 通過一個類的全限定名來獲取定義此類的二進制字節流。
- 將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。
- 會在內存中生成一個代表這個類的 java.lang.Class 對象,作為方法區這個類的各種數據的入口。註意這裏不一定非得要從一個 Class 文件獲取,這裏既可以從 ZIP 包中讀取(比如從 jar 包和 war 包中讀取),也可以在運行時計算生成(動態代理),也可以由其它文件生成(比如將 JSP 文件轉換成對應的 Class 類)
2. 驗證
這一階段的主要目的是為了確保 Class 文件的字節流中包含的信息是否符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。驗證主要包含4個階段的校驗動作:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。
3. 準備
準備階段是正式為類變量分配內存並設置類變量的初始值階段,即在方法區中分配這些變量所使用的內存空間。註意這裏所說的初始值概念,比如一個類變量定義為:
public static int value = 123;
實際上變量 value 在準備階段過後的初始值為 0 而不是 123,將 value 賦值為 123 的 putstatic 指令是程序被編譯後,存放於類構造器方法之中。
public static final int value = 123;
在編譯階段會為 value 生成 ConstantValue 屬性,在準備階段虛擬機會根據 ConstantValue 屬性將 value 賦值為 123。
4. 解析
解析階段是指虛擬機將常量池中的符號引用替換為直接引用的過程。符號引用就是在 class 文件中以: CONSTANT_Class_info、CONSTANT_Field_info
、CONSTANT_Method_info等類型的常量出現。
5. 初始化
初始化階段是類加載最後一個階段,前面的類加載階段之後,除了在加載階段可以自定義類加載器以外,其它操作都由 JVM 主導。到了初始階段,才開始真正執行類中定義的 Java 程序代碼。
那麽,什麽時候開始初始化?
- 使用 new 該類實例化對象的時候;
- 讀取或設置類靜態字段的時候(但被final修飾的字段,在編譯器時就被放入常量池的靜態字段除外static final);
- 調用類靜態方法的時候;
- 使用反射 Class.forName(“xxxx”) 對類進行反射調用的時候,該類需要初始化;
- 初始化一個類的時候,有父類,先初始化父類(註:1. 接口除外,父接口在調用的時候才會被初始化;2.子類引用父類靜態字段,只會引發父類初始化);
- 被標明為啟動類的類(即包含main()方法的類)要初始化;
- 當使用 JDK1.7 的動態語言支持時,如果一個 java.invoke.MethodHandle 實例最後的解析結果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,並且這個方法句柄所對應的類沒有進行過初始化,則需要先觸發其初始化。
以上情況稱為對一個類進行主動引用,且有且只要以上幾種情況需要對類進行初始化。
二、總結
當使用 Java 命令運行 Java 程序時,此時 JVM 啟動,並去方法區下找 Java 命令後面跟的類是否存在,如果不存在,則把類加載到方法區下(還記得運行時數據區域裏的方法區嗎?)
在類加載到方法區時,會分為兩部分:
- 先加載非靜態內容到方法區下的非靜態區域內,再加載靜態內容到方法區下的靜態區域內。
- 當非靜態內容載完成之後,就會加載所有的靜態內容到方法區下的靜態區域內。
上邊這兩部分概括為如下五步:
- 先把所有的靜態內容加載到靜態區域下
- 所有靜態內容加載完之後,對所有的靜態成員變量進行默認初始化
- 當所有的靜態成員變量默認初始化完成之後,再對所有的靜態成員變量顯式初始化
- 當所有的靜態成員變量顯式初始化完成之後,JVM 自動執行靜態代碼塊(靜態代碼塊在棧中執行)[如果有多個靜態代碼,執行的順序是按照代碼書寫的先後順序執行]
- 所有的靜態代碼塊執行完成之後,此時類的加載完成
巴拉巴拉,一堆理論,不夠形象,補張圖
三、練一練
如下是一道真實的面試題,希望上文的理論部分對你有所幫助!
class Singleton{
private static Singleton singleton = new Singleton();
public static int value1;
public static int value2 = 0;
private Singleton(){
value1++;
value2++;
}
public static Singleton getInstance(){
return singleton;
}
}
class Singleton2{
public static int value1;
public static int value2 = 0;
private static Singleton2 singleton2 = new Singleton2();
private Singleton2(){
value1++;
value2++;
}
public static Singleton2 getInstance2(){
return singleton2;
}
}
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println("Singleton1 value1:" + singleton.value1);
System.out.println("Singleton1 value2:" + singleton.value2);
Singleton2 singleton2 = Singleton2.getInstance2();
System.out.println("Singleton2 value1:" + singleton2.value1);
System.out.println("Singleton2 value2:" + singleton2.value2);
}
說出運行的結果:
Singleton1 value1 : 1
Singleton1 value2 : 0
Singleton2 value1 : 1
Singleton2 value2 : 1
Singleton輸出結果:1 0 原因:
- 首先執行main中的Singleton singleton = Singleton.getInstance();
- 類的加載:加載類Singleton
- 類的驗證
- 類的準備:為靜態變量分配內存,設置默認值。這裏為singleton(引用類型)設置為null,value1,value2(基本數據類型)設置默認值0
- 類的初始化(按照賦值語句進行修改):
執行private static Singleton singleton = new Singleton();
執行Singleton的構造器:value1++;value2++; 此時value1,value2均等於1
執行
public static int value1;
public static int value2 = 0;
此時value1=1,value2=0
Singleton2輸出結果:1 1 原因:
- 首先執行main中的Singleton2 singleton2 = Singleton2.getInstance2();
- 類的加載:加載類Singleton2
- 類的驗證
- 類的準備:為靜態變量分配內存,設置默認值。這裏為value1,value2(基本數據類型)設置默認值0,singleton2(引用類型)設置為null,
- 類的初始化(按照賦值語句進行修改):
執行
public static int value2 = 0;
此時value2=0(value1不變,依然是0);
執行
private static Singleton singleton = new Singleton();
執行Singleton2的構造器:value1++;value2++;
此時value1,value2均等於1,即為最後結果
如果文章有錯的地方歡迎指正,大家互相留言交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關註微信公眾號:niceyoo
JVM的類加載機制