java類加載過程

java類加載過程

本篇文章主要是摘抄周志明的《深刻理解java虛擬機》這本書

類加載而後被使用最後被卸載,整個聲明週期包括以下: 加載,鏈接,初始化,使用,卸載。且鏈接中又包括 驗證,準備,解析這個過程。java

加載

"加載"是類加載過程當中的一個階段,在加載階段,虛擬機須要完成如下三個事情數據結構

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

加載階段完成之後虛擬機外部的字節流就按照虛擬機所需的格式存儲在 方法區之中,方法區中的數據存儲格式由虛擬機實現 自行定義,虛擬機未規定此區域的具體數據機構。而後在內存中實例化一個java.lang.Class類的對象(並無規定在java堆中,對於HotSpot 虛擬機而言,Class對象比較特殊,雖然它也是一個對象,可是它存放在方法區中)這個對象將做爲程序訪問方法區中這些類型數據的外部接口。佈局

驗證

  1. 文件格式校驗編碼

    • 是否以魔數0xCAFEBABE開頭
    • 主次版本號是否在當前虛擬機處理的範圍
    • 常量池中是否有不背支持的常量類型
    • 只想常量的各類索引值中是否有指向不存在的常量或者不符合類型的常量
    • CONSTANT_Utf8_info型的常量中是否有不符合UTF-8編碼的格式
    • Class文件中各個部分及文件自己是否有被刪除或者附加的其餘信息
    • ……

    第一輪的驗證遠非這些,上面驗證只是HotSpot中的驗證的一小部分,這部分主要是基於字節流進行驗證的,保證字節流能正確的解析並存儲在方法區以內 ,格式上符合描述一個java類型信息的要求。只有經過這個階段的驗證,字節流纔會進入內存的方法區中進行存儲,因此後面的三個階段所有是基於方法區 的存儲結構進行的,不會再直接操做字節流。翻譯

  2. 元數據校驗
    這一階段是對字節碼描述的信息語義分析,保證其描述的信息符合java語言規範的要求,可能包括的驗證點以下:指針

    • 是否有父類
    • 是否繼承了不容許繼承的類(final修飾)
    • 不是抽象類的話是否實現了接口或者抽象類中的方法
    • 類的字段,方法是否與父類產生衝突,例如複寫了父類的final字段以及不合規的方法重載。
  3. 字節碼驗證
    這個階段是更爲複雜和嚴格的驗證,經過對數據流和控制流的分析,肯定語義是合法的,符合邏輯的。這一階段對類的方法體進行校驗分析,保證被校驗類的方法 在運行時不會產生對虛擬機有害的操做,例如:code

    • 保證跳轉指令不會跳轉到方法體之外的字節碼指令上。
    • 保證任意時刻操做數棧的數據類型與指令代碼的序列都能配合工做。
    • .....
  4. 符號引用驗證 最後一階段的校驗發生在虛擬機將符號引用轉化爲直接引用的時候,這個轉化動做將在鏈接的第三個階段,解析階段中發生。符號引用驗證的目的是確保解析動做能 正常的執行,若是沒法經過符號引用驗證,將會拋出java.lang.NoSuchMethodError,java.lang.NoSuchFieldError,java.lang.IllegalAccessError等 一般會校驗如下內容:對象

    • 符號引用中經過字符串描述的全限定名是否能找到對應的類
    • 在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段
    • 符號引用中的類,字段,方法的可訪問性(private,public,protected,default)是否可被當前類訪問
    • .......

對於虛擬機類加載機制來講,驗證階段是一個很是重要的階段,可是不是一個必須的階段,覺得對程序運行期沒有影響。若是所運行的代碼是很是可信的,已經被反覆的校驗過,能夠考慮使用 -Xverify:none 參數來關閉大部分的類檢驗過程,縮短虛擬機的編譯時間繼承

準備

準備階段是正式爲類變量分配內存地址並設置類變量初始值的階段,這些變量所使用的初始值都將在內存中分配。可是須要注意的是,這時候進行內存分配的僅包括類變量(被static修飾的變量), 並不包括實例變量,實例變量將會在對象實例化的時候隨着對象一塊兒分配在Java堆中。其次,這裏所說的初始值一般狀況下是數據類型的零值,假設有以下定義:索引

public static int value=123;

那變量value在準備階段事後的初始值是0,而不是123,由於這時候還沒有開始執行任何java方法,而把value賦值爲123的putstatic指令是在程序編譯以後,存放於類構造器<clinit>()方法之中,因此 把value賦值爲123的動做將在初始化階段纔會執行。
可是上面所說的一般狀況會把value值設置爲0值,可是若是按照下面定義:

public static final int value=123

這時候再準備階段value值就會被初始化爲Constant-Value屬性所值的值,value就會被賦值爲123

解析

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

  • 符號引用:
    符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時無歧義的定位到目標便可。符號引用與虛擬機實現的內存佈局無關,引用的目標並不必定已經加載到內存中。各類 虛擬機實現的內存佈局能夠各不相同,可是他們能接收的符號引用必須是一致的,由於符號引用的字面量形式明肯定義在了java虛擬機規範的Class文件格式中。
  • 直接引用
    直接引用能夠是直接指向目標的指針,相對偏移量或者是一個能簡介定位到目標的句柄。直接引用是和虛擬機實現的內存佈局相關的,同一個符號引用在不一樣的虛擬機實例上翻譯出來的直接引用通常不會相同。若是有了直接引用, 那引用的目標一定已經在內存中存在。

初始化

類初始化階段是類加載過程的最後一步,前面的類加載過程當中,除了在加載階段用戶引用程序能夠經過自定義類加載器參與以外,其他動做徹底由虛擬機主導和控制。到了初始化階段才真正開始執行類中定義的java代碼

在準備階段,變量已經賦值過一次初始值,而在初始化階段,則會根據程序初始化類變量和他資源。或者說,初始化階段是執行類構造器<clinit>()方法的過程

<clinit>()方法石油編譯器自動收集類中的全部變量的賦值動做和靜態語句塊(static{}塊)中的語句合併產生的,
編譯器收集的順序是由語句在源文件中出現的順序所決定的,靜態語句塊中只能訪問到定義在靜態語句以前的變量,
定義在它以後的變量,在前面靜態語句塊能夠賦值,可是不能訪問。
public static Test{
        static(
            i = 0;//給變量賦值能夠正常的編譯經過
            System.out.print(i); //這句編譯器會提示「非法向前引用」
        )
        static int i=1;
    }
相關文章
相關標籤/搜索