天天看點

JVM的類加載機制

一、基本概念

JVM 類加載機制分為五個部分:加載,驗證,準備,解析,初始化,下面我們就分别來看一下這五個過程。

1. 加載

加載是類加載過程中的一個階段,這個階段虛拟機要完成3件事。

  1. 通過一個類的全限定名來擷取定義此類的二進制位元組流。
  2. 将這個位元組流所代表的靜态存儲結構轉化為方法區的運作時資料結構。
  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 程式代碼。

那麼,什麼時候開始初始化?

  1. 使用 new 該類執行個體化對象的時候;
  2. 讀取或設定類靜态字段的時候(但被final修飾的字段,在編譯器時就被放入常量池的靜态字段除外static final);
  3. 調用類靜态方法的時候;
  4. 使用反射 Class.forName(“xxxx”) 對類進行反射調用的時候,該類需要初始化;
  5. 初始化一個類的時候,有父類,先初始化父類(注:1. 接口除外,父接口在調用的時候才會被初始化;2.子類引用父類靜态字段,隻會引發父類初始化);
  6. 被标明為啟動類的類(即包含main()方法的類)要初始化;
  7. 當使用 JDK1.7 的動态語言支援時,如果一個 java.invoke.MethodHandle 執行個體最後的解析結果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且這個方法句柄所對應的類沒有進行過初始化,則需要先觸發其初始化。

以上情況稱為對一個類進行主動引用,且有且隻要以上幾種情況需要對類進行初始化。

二、總結

當使用 Java 指令運作 Java 程式時,此時 JVM 啟動,并去方法區下找 Java 指令後面跟的類是否存在,如果不存在,則把類加載到方法區下(還記得運作時資料區域裡的方法區嗎?)

在類加載到方法區時,會分為兩部分:

  1. 先加載非靜态内容到方法區下的非靜态區域内,再加載靜态内容到方法區下的靜态區域内。
  2. 當非靜态内容載完成之後,就會加載所有的靜态内容到方法區下的靜态區域内。

上邊這兩部分概括為如下五步:

  1. 先把所有的靜态内容加載到靜态區域下
  2. 所有靜态内容加載完之後,對所有的靜态成員變量進行預設初始化
  3. 當所有的靜态成員變量預設初始化完成之後,再對所有的靜态成員變量顯式初始化
  4. 當所有的靜态成員變量顯式初始化完成之後,JVM 自動執行靜态代碼塊(靜态代碼塊在棧中執行)[如果有多個靜态代碼,執行的順序是按照代碼書寫的先後順序執行]
  5. 所有的靜态代碼塊執行完成之後,此時類的加載完成

巴拉巴拉,一堆理論,不夠形象,補張圖

三、練一練

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);

}      
Singleton輸出結果:1 0 原因:
  1. 首先執行main中的Singleton singleton = Singleton.getInstance();
  2. 類的加載:加載類Singleton
  3. 類的驗證
  4. 類的準備:為靜态變量配置設定記憶體,設定預設值。這裡為singleton(引用類型)設定為null,value1,value2(基本資料類型)設定預設值0
  5. 類的初始化(按照指派語句進行修改):

    執行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 原因:
  1. 首先執行main中的Singleton2 singleton2 = Singleton2.getInstance2();
  2. 類的加載:加載類Singleton2
  3. 類的準備:為靜态變量配置設定記憶體,設定預設值。這裡為value1,value2(基本資料類型)設定預設值0,singleton2(引用類型)設定為null,
  4. 此時value2=0(value1不變,依然是0);

    private static Singleton singleton = new Singleton();

    執行Singleton2的構造器:value1++;value2++;

    此時value1,value2均等于1,即為最後結果

jvm