在JVM中是如何加載一個類的

前言

Java源代碼被編譯成class字節碼,最終須要加載到虛擬機中才能運行。整個加載過程包括:加載、驗證、準備、解析、初始化5個階段,其中準備、驗證、解析爲連接的子階段,java

加載過程

加載(讀取)

一、經過一個類的全限定名獲取描述此類的二進制字節流;
二、將這個字節流所表明的靜態存儲結構保存爲方法區的運行時數據結構;
三、在java堆中生成一個表明這個類的java.lang.Class對象,做爲訪問方法區的入口;數據結構

虛擬機設計團隊把加載動做放到JVM外部實現,以便讓應用程序決定如何獲取所需的類,實現這個動做的代碼稱爲「類加載器」,JVM提供了3種類加載器:
一、啓動類加載器(Bootstrap ClassLoader):負責加載 JAVA_HOME\lib 目錄中的,或經過-Xbootclasspath參數指定路徑中的,且被虛擬機承認(按文件名識別,如rt.jar)的類。
二、擴展類加載器(Extension ClassLoader):負責加載 JAVA_HOME\lib\ext 目錄中的,或經過java.ext.dirs系統變量指定路徑中的類庫。
三、應用程序類加載器(Application ClassLoader):負責加載用戶路徑(classpath)上的類庫。多線程

JVM基於上述類加載器,經過雙親委派模型進行類的加載,固然咱們也能夠經過繼承java.lang.ClassLoader實現自定義的類加載器。spa

雙親委派模型工做過程:當一個類加載器收到類加載任務,優先交給其父類加載器去完成,所以最終加載任務都會傳遞到頂層的啓動類加載器,只有當父類加載器沒法完成加載任務時,纔會嘗試執行加載任務。線程

雙親委派模型有什麼好處?
好比位於rt.jar包中的類java.lang.Object,不管哪一個加載器加載這個類,最終都是委託給頂層的啓動類加載器進行加載,確保了Object類在各類加載器環境中都是同一個類。
設計

驗證

爲了確保Class文件符合當前虛擬機要求,須要對其字節流數據進行驗證,主要包括格式驗證、元數據驗證、字節碼驗證和符號引用驗證。指針

1.格式驗證
驗證字節流是否符合class文件格式的規範,而且能被當前虛擬機處理,如是否以魔數0xCAFEBABE開頭、主次版本號是否在當前虛擬機處理範圍內、常量池是否有不支持的常量類型等。只有通過格式驗證的字節流,纔會存儲到方法區的數據結構,剩餘3個驗證都基於方法區的數據進行。code

2.元數據驗證
對字節碼描述的數據進行語義分析,以保證符合Java語言規範,如是否繼承了final修飾的類、是否實現了父類的抽象方法、是否覆蓋了父類的final方法或final字段等。對象

3.字節碼驗證
對類的方法體進行分析,確保在方法運行時不會有危害虛擬機的事件發生,如保證操做數棧的數據類型和指令代碼序列的匹配、保證跳轉指令的正確性、保證類型轉換的有效性等。blog

4.符號引用驗證
爲了確保後續的解析動做可以正常執行,對符號引用進行驗證,如經過字符串描述的全限定名是都能找到對應的類、在指定類中是否存在符合方法的字段描述符等。

準備

在準備階段,爲類變量(static修飾)在方法區中分配內存並設置初始值。

private static int var = 100;

準備階段完成後,var 值爲0,而不是100。在初始化階段,纔會把100賦值給var,

可是有個特殊狀況:

private static final int VAL= 100;

在編譯階段會爲VAL生成ConstantValue屬性,在準備階段虛擬機會根據ConstantValue屬性將VAL賦值爲100。

解析

解析階段是將常量池中的符號引用替換爲直接引用的過程,符號引用和直接引用有什麼不一樣?
一、符號引用使用一組符號來描述所引用的目標,能夠是任何形式的字面常量,定義在Class文件格式中。
二、直接引用能夠是直接指向目標的指針、相對偏移量或則能間接定位到目標的句柄。

初始化

初始化階段是執行類構造器<clinit>方法的過程,<clinit>方法由類變量的賦值動做和靜態語句塊按照在源文件出現的順序合併而成,該合併操做由編譯器完成。

private static int value = 100;
static int a = 100;
static int b = 100;
static int c;
 
static {
    c = a + b;
    System.out.println("it only run once");
}

一、<clinit>方法對於類或接口不是必須的,若是一個類中沒有靜態代碼塊,也沒有靜態變量的賦值操做,那麼編譯器不會生成<clinit>; 二、<clinit>方法與實例構造器不一樣,不須要顯式的調用父類的<clinit>方法,虛擬機會保證父類的<clinit>優先執行; 三、爲了防止屢次執行<clinit>,虛擬機會確保<clinit>方法在多線程環境下被正確的加鎖同步執行,若是有多個線程同時初始化一個類,那麼只有一個線程可以執行<clinit>方法,其它線程進行阻塞等待,直到<clinit>執行完成。 四、注意:執行接口的<clinit>方法不須要先執行父接口的<clinit>,只有使用父接口中定義的變量時,纔會執行。

相關文章
相關標籤/搜索