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

虛擬機類加載機制------類加載的過程

字段表 字節 歧義 load 存儲 復雜 安全 定位 sch

1.加載

虛擬機需要幹三件事:

①、通過一個類的的全限定名來獲取定義此類的二進制字節流(沒有規定二進制字節流從那裏獲取,怎樣獲取,許多java技術也都建立在這基礎上)

②將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構(將常量池轉變成運行時常量池)

③在內存中生成一個代表這個類的java.lang.Class對象,作為方法區著各類的各種數據的訪問入口。

相比較於類加載過程的其他階段,非數組類獲取類的二進制字節流的動作是開發人員可控性最強的,因為加載階段既可以使用系統提供的引導類加載器來完成,也可以由用戶自定義的類加載器去完成

,開發人員可以自己重寫一個類加載器的loadClass()方法

對於數組類,不通過類加載器創建,由java虛擬機直接創建的。但是數組類的元素類型(去掉所有維度的類型)最終是要通過類加載器去創建。

數組類的創建過程要遵循以下原則:

①、如果數組的組件類型(數組去掉一個維度的類型)是引用類型,就遞歸家在過程去加載這個組件類型,數組C將在加載該組件類型的類加載器的類名稱空間上被標識。

②、如果數組的組件類型不是引用類型(例如int[]數組),java虛擬機將會把數組C標記為與引導類加載器關聯

③、數組類的可見性與它的組件類型的可見性一致,如果組件類型不是引用類型,那數組類的可見性將默認為public

加載階段完成後,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,方法區中的數據存儲格式由虛擬機自行定義。然後在內存中實例化一個java.lang.Classleide duix (沒有明確規定在java堆中嗎、,對於Hotspot來說,Class對象比較特殊,存放在方法區裏面)。這個對象作為程序訪問方法區中這些類型數據的外部接口。

連接階段:

2 .驗證

驗證的目的是確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機的安全。

整體上看,驗證階段大致上會完成下面四個階段的檢驗工作:

(1)基於字節流檢驗

文件格式驗證:基於二進制字節流進行的,只有經過這個階段的驗證,字節流才會進入內存的方法區中存儲。目的是保證輸入的字節流能正確解析並存儲於方法區之內

(2)基於方法區的存儲結構

元數據驗證:對字節碼描述的信息進行語義分析,保證不存在不符合JAVA語言規範的元數據信息。

字節碼驗證:最復雜的一個階段,對類的方法體進行校驗,保證被叫嚴磊的方法在運行時不會做出還虛擬機的事。目的是通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的。

符號引用驗證:發生在虛擬機將符號引用轉化為直接引用的時候,這個轉化動作將在解析階段中發生。目的是確保解析動作能正常執行。

3.準備

準備階段是正式為類變量分配內存並設置初始值的階段,這些變量所使用的內存都在方法區中進行分配。

註意兩點:一點是類變量,另一點是初始值“通常情況下”是數據類型的零值。例如“public static int value=123”在準備過程value的值為0而不是123,因為這時候尚未開始執行任何java方法,而把value賦值為123的puststatic指令是程序被變異後,存放於類構造器<clint>()方法之中,所以把value賦值為123的動作在初始化時候才會進行

特殊:如果類字段的字段屬性表中存在ConstacntValue屬性,那麽準備階段變量value就會被初始化為所指定的值

public static final int value=123;

這是value被賦值為123.

4.解析

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

符號引用和直接引用又有什麽關聯呢?

符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時可以無歧義的定位到目標即可。符號引用與虛擬機實現的內存布局無關,引用的目標並不一定已經加載到內存中,各種虛擬機實現的內存布局可以各不相同,但是它們能接收的符號引用必須都是一致的,因為符號引用的字面量形式明確定義在java虛擬機規範的Class文件格式中。

直接引用:直接引用可以是直接指向目標的指針、相對偏移量或是一個嫩詳見定位到目標的句柄。直接引用時和虛擬機實現的內存布局相關的,通過一個符號引用在不同虛擬機實例上翻譯出來的直接引用一般不會相同。如果有了直接引用,被引用的目標必在內存中存在。

解析的具體時間沒有規定,只要求了在執行anewarray checkcast getfield getstatic instanceof invokedynamic invokeinterface invokespecial invokestatic invokevirtual ldc multianewarray new putfield putstatic 這十六個用於操作符號引用的字節碼指令之前,先對他們所使用的符號引用進行解析

對一個符號引用進行多次解析請求,除了invokedynamic指令以外,虛擬機可以緩存第一次解析的結果,之後再請求解析,可以直接調用避免重復進行。

invokedynamic指令是“動態調用點限定符”動態也就是必須等到程序實際運行到這條指令的時候,解析才進行的。

解析動作主要針對

類或接口解析

假設當前代碼所處的類為D,如果要把一個從未解析過的符號引用N解析為一個類或接口C的直接引用,那虛擬機完成這個解析的過程需要以下3個步驟

①:C不是一個數組類型,虛擬機會把代表N的全限定名傳遞給D的類加載器去加載這個類C。在加載過程中,由於元數據驗證、字節碼驗證的需要,有可能觸發其他相關類的加載動作。

②:如果C是一個數組類型,並且數組的元素類型為對象,例如N是“[Ljava/lang/Integer”的形式,那將會按照第1點的規則加載數組元素類型,如果N的描述符是前面那樣,需要加載的元素類型就是“java.lang,Integer”,接著由虛擬機生一個代表此數組維度和元素的數組對象。

③:如果上面的步驟沒有出現異常,C已經在虛擬機中成為了一個有效的類和接口了,但是解析完成之前還有進行符號引用驗證,確保D是否具備對C的訪問權限。

字段解析:

要解析字段符號引用,首先要對字段表內字段所屬的類或接口的符號引用進行解析,如果解析成功,那這個字段所屬的類或接口用C表示,虛擬機規範要求安好如下步驟對C進行後續字段的搜索

①:如果C本身就包含了簡單名稱和字段描述符都與目標相匹配的字段,則返回這個字段的直接引用,查找結束

②:否則如果在C中實現了接口,將會按照繼承關系從下往上遞歸搜索各個接口和它的父接口,如果接口中包含了簡單名稱和字段描述符都與目標相匹配的字段,則返回這個字段的直接引用,查找結束

③:否則,如果C不是java.lang.Object的話,將會按照繼承關系從下往上遞歸搜索其父類,如果父類包含了簡單名稱和字段描述符都與目標相匹配的字段,則返回這個字段的直接引用,查找結束

④:否則查找失敗,拋出異常java.lang.NoSuchMethodError

⑤::如果上面的步驟沒有出現異常,但是解析完成之前還有進行符號引用驗證,確保是否具備對字段的訪問權限。

public class FieldResolution{

  interface Interface0{
     intA=1;
  }
  interface Interface1 extends Interface0{

  int A=2;
}
static class Parent implements Interface1{
    public static int A=3;
}
public static void main(Strings[] args){
  System.out.println(Parent.A);

}

}

如果一個字段出現在C的接口和父類中,或者同時在自己或父類的多個接口中出現,那編譯器將可能拒絕編譯

類方法解析:

要解析類方法符號引用,首先要對類方法表中方法所屬的類或接口的符號引用進行解析,如果解析成功,那這個方法所屬的類或接口用C表示,虛擬機規範要求安好如下步驟對C進行後續類方法的搜索

①:類方法和接口房符號引用的常量類型定義是分開的,如果在類方法表中發現class_index中索引的C是個接口,直接拋出java.lang.IncompatibleClassChangeError異常

②:如果C本身就包含了簡單名稱和描述符都與目標相匹配的方法,則返回這個方法的直接引用,查找結束

③:否則,在C的父類中遞歸查找,如果父類包含了簡單名稱和描述符都與目標相匹配的方法,則返回這個方法的直接引用,查找結束

④:在類C實現的接口列表及它們的父接口之中遞歸查找是否有簡單名稱和描述符都與目標相匹配的方法, 如果存在匹配的方法,說明類C是一個抽象類,這時查找結束,拋出java.lang,AbstractMethodError異常

④:否則查找失敗,拋出異常java.lang.NoSuchMethodError

⑤::如果上面的步驟沒有出現異常,但是解析完成之前還有進行符號引用驗證,確保是否具備對方法的訪問權限。

接口方法解析:

方法類型解析:

方法句柄解析:

調用點限定符解析:

7類符號引用進行解析

虛擬機類加載機制------類加載的過程