JVM的類加載機制

1、基本概念

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

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。app

4. 解析

解析階段是指虛擬機將常量池中的符號引用替換爲直接引用的過程。符號引用就是在 class 文件中以: CONSTANT_Class_info、CONSTANT_Field_info
、CONSTANT_Method_info等類型的常量出現。spa

5. 初始化

初始化階段是類加載最後一個階段,前面的類加載階段以後,除了在加載階段能夠自定義類加載器之外,其它操做都由 JVM 主導。到了初始階段,纔開始真正執行類中定義的 Java 程序代碼。代理

那麼,何時開始初始化?code

  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 的方法句柄,而且這個方法句柄所對應的類沒有進行過初始化,則須要先觸發其初始化。

以上狀況稱爲對一個類進行主動引用,且有且只要以上幾種狀況須要對類進行初始化。orm

2、總結

當使用 Java 命令運行 Java 程序時,此時 JVM 啓動,並去方法區下找 Java 命令後面跟的類是否存在,若是不存在,則把類加載到方法區下(還記得運行時數據區域裏的方法區嗎?)

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

  1. 先加載非靜態內容到方法區下的非靜態區域內,再加載靜態內容到方法區下的靜態區域內。
  2. 當非靜態內容載完成以後,就會加載全部的靜態內容到方法區下的靜態區域內。

上邊這兩部分歸納爲以下五步:

  1. 先把全部的靜態內容加載到靜態區域下
  2. 全部靜態內容加載完以後,對全部的靜態成員變量進行默認初始化
  3. 當全部的靜態成員變量默認初始化完成以後,再對全部的靜態成員變量顯式初始化
  4. 當全部的靜態成員變量顯式初始化完成以後,JVM 自動執行靜態代碼塊(靜態代碼塊在棧中執行)[若是有多個靜態代碼,執行的順序是按照代碼書寫的前後順序執行]
  5. 全部的靜態代碼塊執行完成以後,此時類的加載完成

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

3、練一練

以下是一道真實的面試題,但願上文的理論部分對你有所幫助!

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 緣由:
  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. 類的驗證
  4. 類的準備:爲靜態變量分配內存,設置默認值。這裏爲value1,value2(基本數據類型)設置默認值0,singleton2(引用類型)設置爲null,
  5. 類的初始化(按照賦值語句進行修改):
    執行
    public static int value2 = 0;
    此時value2=0(value1不變,依然是0);
    執行
    private static Singleton singleton = new Singleton();
    執行Singleton2的構造器:value1++;value2++;
    此時value1,value2均等於1,即爲最後結果

若是文章有錯的地方歡迎指正,你們互相留言交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠關注微信公衆號:niceyoo

相關文章
相關標籤/搜索