Java 虛擬機器之四:Java類載入機制

百聯達發表於2018-09-03

一:前言

Java語言的型別可以分為兩大類:基本型別和引用型別。

基本型別 包括byte,short,int,long,float,double,boolean,char。

引用型別 包括類,介面,陣列類和泛型引數。由於泛型引數會在編譯過程中被擦除,因此Java虛擬機器實際上只有前三種。在類,介面和陣列類中,陣列類是由Java虛擬機器直接生成的,其它兩種則有對應的位元組流。

二:載入

1.載入,就是查詢位元組流,並且據此生成一個代表這個類的java.lang.Class物件的過程。注意這裡位元組流不一定非得要從一個Class檔案獲取,這裡既可以從ZIP包中讀取(比如從jar包和war包中讀取),也可以在執行時計算生成(動態代理),也可以由其它檔案生成(比如將JSP檔案轉換成對應的Class類)。載入的資訊儲存在JVM的方法區。

2.對於陣列類來說,它並沒有對應的位元組流,而是由Java虛擬機器直接生成的。對於其它的類來說,Java虛擬機器則需要藉助類載入器來完成查詢位元組流的過程。

3.類載入器有兩種,一種是啟動類載入器,其它的類載入器都是java.lang.ClassLoader的子類。啟動類載入器是由C++實現的,沒有對應的Java物件,因此在Java中只能用null代替。除了啟動類載入器之外,另外兩個重要的類載入器是擴充套件類載入器和應用類載入器,均由Java核心類庫提供。 啟動類載入器載入最為基礎,最為重要的類,如JRE的lib目錄下jar包中的類;擴充套件類載入器的父類是啟動類載入器,它負責載入相對次要,但又通用的類,如JRE的lib/ext目錄下jar包中的類;應用類載入器的父類載入器則是擴充套件類載入器,它負責載入應用程式路徑下的類。

4.JVM透過雙親委派模型進行類的載入,當然我們也可以透過繼承java.lang.ClassLoader實現自定義的類載入器。當一個類載入器收到類載入任務,會先交給其父類載入器去完成,因此最終載入任務都會傳遞到頂層的啟動類載入器,只有當父類載入器無法完成載入任務時,才會嘗試執行載入任務。

採用雙親委派的一個好處是比如載入位於rt.jar包中的類java.lang.Object,不管是哪個載入器載入這個類,最終都是委託給頂層的啟動類載入器進行載入,這樣就保證了使用不同的類載入器最終得到的都是同樣一個Object物件。

三:連結

連結,是指將建立成的類合併至Java虛擬機器中,使之能夠執行的過程。它分為驗證,準備和解析三個階段。

1.驗證

 該階段的目的在於確保被載入類能夠滿足Java虛擬機器的約束條件。

2.準備

該階段的目的是為被載入類的靜態欄位分配記憶體,即在方法區中分配這些變數所使用的記憶體空間。Java程式碼中對靜態欄位的具體初始化,則會在稍後的初始化階段中進行。

3.解析

 在class檔案被載入至Java虛擬機器前,這個類無法知道其它類及其方法,欄位所對應的具體地址。甚至不知道自己方法,欄位的地址。因此,每當需要引用這些成員時,

Java編譯器會生成一個符號引用。在執行階段,這個符號引用一般能夠無歧義地定位到具體目標上。解析階段的目的,正是將這些符號引用解析為實際引用。如果符號引用

指向一個未被載入的類,或者未被載入類的欄位或方法,那麼解析將觸發這個類的載入。

四:初始化

1.初始化階段是類載入最後一個階段,前面的類載入階段之後,除了在載入階段可以自定義類載入器以外,其它操作都由JVM主導。到了初始階段,才開始真正執行類中定義的Java程式程式碼。

2.在 Java 程式碼中,如果要初始化一個靜態欄位,我們可以在宣告時直接賦值,也可以在靜態程式碼塊中對其賦值。

如果直接賦值的靜態欄位被 final 所修飾,並且它的型別是基本型別或字串時,那麼該欄位便會被 Java 編譯器標記成常量值(ConstantValue),其初始化直接由 Java 虛擬機器完成。除此之外的直接賦值操作,以及所有靜態程式碼塊中的程式碼,則會被 Java 編譯器置於同一方法中,並把它命名為 < clinit >。

類載入的最後一步是初始化,便是為標記為常量值的欄位賦值,以及執行 < clinit > 方法的過程。Java 虛擬機器會透過加鎖來確保類的 < clinit > 方法僅被執行一次。

只有當初始化完成之後,類才正式成為可執行的狀態。

3.JVM 規範列舉類的初始化被觸發的情況:

a.當虛擬機器啟動時,初始化使用者指定的主類;

b.當遇到用以新建目標類例項的 new 指令時,初始化 new 指令的目標類;

c.當遇到呼叫靜態方法的指令時,初始化該靜態方法所在的類;

d.當遇到訪問靜態欄位的指令時,初始化該靜態欄位所在的類;

e.子類的初始化會觸發父類的初始化;

f.如果一個介面定義了 default 方法,那麼直接實現或者間接實現該介面的類的初始化,會觸發該介面的初始化;

j.使用反射 API 對某個類進行反射呼叫時,初始化這個類;

h.當初次呼叫 MethodHandle 例項時,初始化該 MethodHandle 指向的方法所在的類。

五:總結

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28624388/viewspace-2213484/,如需轉載,請註明出處,否則將追究法律責任。

相關文章