1. 程式人生 > >Java虛擬機的類加載機制

Java虛擬機的類加載機制

類加載 imp 接受 field 合規 結構 個人 strip 而不是

一、虛擬機的類加載機制

我們先看看類加載機制的定義,再來說法這一個加載流程。《深入理解JVM虛擬機》第二版中是這麽解釋的:虛擬機吧描述類的數據從Class問價加載到內存並對數據進行校驗/轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。
另外要註意的是,Java語言中,類型的加載/連接和初始化過程都是在程序運行期間完成的,這個相當於C++中的鏈接過程。

二、類的生命周期

然後每個Class文件都有可能待變Java語言中的一個類或者接口,我個人理解,Java中類和接口本質是一樣的。另外Class文件在jvm中可以是各種形式的,只要是二進制流就可以,不一定需要是本地磁盤文件。
技術分享圖片


上圖中類加載、驗證、準備、初始化和卸載這五個階段的順序是確定的,累的加載過程必須按照這種順序按部就班的開始,而解析的過程則不一定。

三、什麽時候開始加載

虛擬機規範中沒有要求,所以不同的虛擬機可以不同的實現方式,但是初始化時嚴格規定必須對類立刻進行初始化

  1. 遇到new、getstatic、putstatic或invokestatic這四條字節指令碼,如果類沒有進行初始化,則需要先觸發其初始化。
  2. 使用java.lang.reflect包的方法對類進行反射調用的時候,與上相似。
  3. 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
  4. 當虛擬機啟動時,用戶需要制定一個要執行的主類,虛擬機會先初始化(包含main函數)這個主類。
  5. JDK1.7中,如果一個java.lang.invoke.MetodHandle實例最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,並且這個句柄所對應的類沒有過進行過初始化,則需要初始化。

這裏要註意的是,雖然沒有說什麽時候要加載,但當需要初始化的時候,必須要按上圖的順序,逐步進行。但是進行不一定是同步的,可能是交叉的,例如解析可能是可以在初始化之後的,就是說我開始了解析之後,可能不會完成,而是先去初始化,再進行解析。

除此之外,所有引用類的方式都不會觸發其初始化,成為被動引用。

四、類的加載過程

加載

加載類加載過程的第一個階段(整一個從加載到初始化的這麽一個過程稱為類加載),這個階段需要完成三件事。

  1. 通過一個類的全限定名來獲取定義此類的二進制字節流。
  2. 將這個字節流所代表的的靜態存儲結構轉化為方法去的運行時數據結構。
  3. 在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口。
    另外,類加載時通過類加載器完成的,具體由什麽加載器可以用開發人員控制,開發人員既可以使用系統的引導類加載器,也可以用戶自定義的類加載器完成。(重寫一個Classloader)
    對於數組而言,數組類並不是通過類加載來創建的,但是數組元素是通過類加載器創建的。具體規則請查閱《深入理解jvm虛擬機第二版》P215,裏面詳細介紹了關於數組類的加載機制。

驗證

驗證是圖1中,連接階段的第一個步驟,目的是保證Class文件的字節流中包含的信息符合虛擬機的規範,如果不符合規範,並且保證虛擬機的安全就會報錯。驗證階段大致上是分成四個階段:

1. 文件格式驗證

第一階段要驗證字節流是否符合Class文件格式的而規範,並且被當前版本的虛擬機處理。

2. 元數據驗證

第二階段是對字節碼描述的信息進行語義分析,保證其描述的信息符合Java語言的規範要求。

3. 字節碼驗證

第三階段是通過數據流和控制語句分析,是否存在邏輯錯誤,語義是否合法。

4. 符號引用驗證

最後一個階段是發生在虛擬機將符號引用轉化為直接引用的時候,這個轉化動作將在連接的第三階段——解析階段中發生。符號引用驗證可以看做是對類自身以外的信息進行匹配性校驗,目的是為了確保解析動作能正常執行。(是否可以正確找到引用的類)

準備

準備階段是正式為類變量分配內存病設置類變量初始值的階段(static變量)這些變量所使用的內存都講在方法去中進行分配。這個階段中有兩個容易產生混淆的概念需要強調一下。

  • 首先,這時候進行內存分配的僅包括類變量,不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在Java堆中。
  • 其次,這裏所說的初始值通常情況下是數據類型的零值。如下
    public static int value = 123;
    這個階段的賦值實際上是初始值0,而不是123,123是在初始化階段才賦值的,也就是clint(並非new初始化,而是類初始化clint()方法)。

解析

在JVM中類加載過程中,在解析階段,Java虛擬機會把類的二級制數據中的符號引用替換為直接引用。

符號引用(Symbolic References):

  符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能夠無歧義的定位到目標即可。例如,在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等類型的常量出現。符號引用與虛擬機的內存布局無關,引用的目標並不一定加載到內存中。在Java中,一個java類將會編譯成一個class文件。在編譯時,java類並不知道所引用的類的實際地址,因此只能使用符號引用來代替。比如org.simple.People類引用了org.simple.Language類,在編譯時People類並不知道Language類的實際內存地址,因此只能使用符號org.simple.Language(假設是這個,當然實際中是由類似於CONSTANT_Class_info的常量來表示的)來表示Language類的地址。各種虛擬機實現的內存布局可能有所不同,但是它們能接受的符號引用都是一致的,因為符號引用的字面量形式明確定義在Java虛擬機規範的Class文件格式中。

直接引用:

直接引用可以是
(1)直接指向目標的指針(比如,指向“類型”【Class對象】、類變量、類方法的直接引用可能是指向方法區的指針)
(2)相對偏移量(比如,指向實例變量、實例方法的直接引用都是偏移量)
(3)一個能間接定位到目標的句柄
直接引用是和虛擬機的布局相關的,同一個符號引用在不同的虛擬機實例上翻譯出來的直接引用一般不會相同。如果有了直接引用,那引用的目標必定已經被加載入內存中了。

初始化

類初始化階段是類加載過程的最後一步,實際上就是執行clinit()方法的過程。和init方法不同,clinit是對類進行初始化,而java類文件中,init()方法是在構造對象的時候,對對象進行初始化,這點一定要分清楚。

五、類加載器

未完成。。。

Java虛擬機的類加載機制