虛擬機類加載機制

Java虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,這個過程被稱做虛擬機的類加載機制。 ——《深刻理解Java虛擬機》html

類加載的時機

類的生命週期:

加載、鏈接(驗證、準備、解析)、初始化、使用、卸載。 類的生命週期java

這五個階段的順序是肯定的,類加載必須按照這種順序「開始」(這些階段一般是交叉混合進行的,因此執行順序與完成順序可能並非按照開始順序)。web

第一階段「加載」啥時候開始?

《Java虛擬機規範》中並無進行強制約束,可是對於初始化階段,則是嚴格規定了有且只有六種狀況必須當即對類進行「初始化」(而加載、驗證、準備天然須要在此之 前開始):緩存

  1. 遇到new、getstatic、putstatic或invokestatic這四條字節碼指令時,若是類型沒有進行過初始化,則須要先觸發其初始化階段。可以生成這四條指令的典型Java代碼場景有:
    1. 使用new關鍵字實例化對象的時候。
    2. 讀取或設置一個類型的靜態字段(被final修飾、已在編譯期把結果放入常量池的靜態字段除外)的時候。
    3. 調用一個類型的靜態方法的時候。
  2. 使用java.lang.reflect包的方法對類型進行反射調用的時候,若是類型沒有進行過初始化,則需 要先觸發其初始化。
  3. 當初始化類的時候,若是發現其父類尚未進行過初始化,則須要先觸發其父類的初始化。
  4. 當虛擬機啓動時,用戶須要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先 初始化這個主類。
  5. 當使用JDK 7新加入的動態語言支持時,若是一個java.lang.invoke.MethodHandle實例最後的解 析結果爲REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四種類型的方法句 柄,而且這個方法句柄對應的類沒有進行過初始化,則須要先觸發其初始化。
  6. 當一個接口中定義了JDK 8新加入的默認方法(被default關鍵字修飾的接口方法)時,若是有這個接口的實現類發生了初始化,那該接口要在其以前被初始化。

接口初始化與類初始化不一樣,只要是體如今上述第3點,當一個接口在初始化時,並不要求其父接口所有都完成了初始化,只有在真正使用到父接口的時候(如引用接口中定義的常量)纔會初始化。安全

類加載的過程

加載

在加載階段,Java虛擬機須要完成如下三件事情:數據結構

  1. 經過一個類的全限定名來獲取定義此類的二進制字節流。
  2. 將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。
  3. 在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入 口。

總結一下,就是讀取Class文件,將類結構存到方法區,再在堆內建立一個Class對象。oracle

鏈接

驗證

驗證是鏈接階段的第一步,這一階段的目的是確保Class文件的字節流中包含的信息符合《Java虛 擬機規範》的所有約束要求,保證這些信息被看成代碼運行後不會危害虛擬機自身的安全。jvm

  1. 文件格式驗證
  2. 元數據驗證
  3. 字節碼驗證
  4. 符號引用驗證

準備

準備階段是正式爲類中定義的變量(即靜態變量,被static修飾的變量)分配內存並設置類變量初始值的階段。編輯器

🌰1
//對於static變量來講,在準備階段結束以後,value的值是0,而非123,等到初始化時纔會設置成123。 public static int value = 123;  🌰2 //而對於常量來講,在準備階段就會被設置成123。 public final static int value = 123; 複製代碼

解析

解析階段是Java虛擬機將常量池內的符號引用替換爲直接引用的過程。url

符號引用就是字符串,這個字符串包含足夠的信息,以供實際使用時能夠找到相應的位置。你好比說某個方法的符號引用,如:「java/io/PrintStream.println:(Ljava/lang/String;)V」。裏面有類的信息,方法名,方法參數等信息。

關於符號引用能夠看看R大的回答。JVM裏的符號引用如何存儲?

還有《Java虛擬機規範》第四章class文件格式的第四節常量池 有介紹符號引用的格式。

初始化

初始化階段就是執行類構造器clinit()方法的過程。

何爲clinit()

clinit()方法是由編譯器自動收集類中的全部類變量的賦值動做和靜態語句塊(static{}塊)中的語句合併產生的。就是static變量和static方法合併後的結果。

以下圖,static的變量賦值和方法會被合併到static{}(javap反編譯的結果,static{}就是clinit())裏。

clinit()方法
clinit()方法

類加載器

  1. BootstrapClassLoader在HotSpot中是用C++實現的,是虛擬機的一部分,負責加載JDK/jre/lib下的
  2. ExtClassLoader負責加載JDK/jre/lib/ext, java.ext.dirs系統變量指定的路徑中的全部類庫
  3. AppClassLoader負責加載用戶類路勁下所指定的類

類加載機制

  • 全盤負責,當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其餘Class也將由該類加載器負責載入,除非顯示使用另一個類加載器來載入
  • 父類委託,先讓父類加載器試圖加載該類,只有在父類加載器沒法加載該類時才嘗試從本身的類路徑中加載該類
  • 緩存機制,緩存機制將會保證全部加載過的Class都會被緩存,當程序中須要使用某個Class時,類加載器先從緩存區尋找該Class,只有緩存區不存在,系統纔會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩存區。這就是爲何修改了Class後,必須重啓JVM,程序的修改纔會生效

雙親委派模型

  1. 當AppClassLoader加載一個class時,它首先不會本身去嘗試加載這個類,而是把類加載請求委派給父類加載器ExtClassLoader去完成。

  2. 當ExtClassLoader加載一個class時,它首先也不會本身去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader去完成。

  3. 若是BootStrapClassLoader加載失敗(例如在$JAVA_HOME/jre/lib裏未查找到該class),會使用ExtClassLoader來嘗試加載;

  4. ExtClassLoader也加載失敗,則會使用AppClassLoader來加載,若是AppClassLoader也加載失敗,則會報出異常ClassNotFoundException

使用雙親委派模型的好處

防止重複加載,防止核心庫被修改。

使同名類(🌰java.lang.Object)在程序的各類類加載器環境中都可以保證是同一個類。

參考

《深刻理解Java虛擬機》《Java虛擬機規範

圖片來源《深刻理解Java虛擬機》

本文使用 mdnice 排版

相關文章
相關標籤/搜索