深入理解JVM之四:類加載過程

      虛擬機類加載機制概念:虛擬機把描述類的數據的class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的java類型。首先,先來思考以下兩個問題:

      (1)虛擬機如何加載class文件;

      (2)class文件中的信息進入到虛擬機後發生什麼變化

     相信看完這篇博文對回答這兩個問題會很容易以及對類加載過程有個深刻的理解。


     上圖包括了類加載的全過程:加載、驗證、準備、初始化、解析、使用、卸載。那麼在什麼情況下會執行類的加載過程呢?主要有以下四種情況會執行類的加載過程:

      >使用new去實例化一個對象、讀取或設置一個靜態字段、調用一個靜態方法
      >對類進行反射調用
      >初始化一個類時其父類還沒有初始化
      >虛擬機啓動,用戶需要指定一個要執行的主類等

類加載階段

      1、通過一個類的全限定名來獲取定義此類中的二進制字節流;2、將這個二進制字節流所代表的靜態存儲格式轉化爲方法區運行的數據結構;3、在內存中生成一個java.lang.class對象作爲各種數據的入口;

驗證階段

       這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求(java語言本身是相對安全的語言,但class文件並不一定要求用java源碼編譯而來)驗證階段大致上會完成下面4個階段的驗證動作:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。
      >文件格式驗證:驗證輸入的文件流是否符合Class文件格式規範,這一步保證輸入的文件流能夠正確的解析並且存儲在方法區之內。注意:通過這個階段的驗證,字符流纔會真正的進入到方法區中存儲的,後面的3個驗證階段是基於方法區的存儲結構進行的不會真正的操作字節流。驗證點:是否以魔術0xCAFEBABE開頭、主次版本號是否在當前的虛擬機範圍之內、常量池的常量是否有不被支持的常量類型。
      >元數據驗證:這個階段是對字節碼描述的信息進行語義分析,以保證其描述的信息符合java語言規範的要求。驗證點:這個類是否有父類、這個類的父類是否不允許繼承、這個類是否是抽象類是否實現父類或接口之中要求的方法等
      >字節碼驗證:這個階段的目的是通過數據流和控制流分析確定程序語義是否合法、符合邏輯。驗證點:保證任意操作棧的數據類型和指令碼序列都能配合工作、保證跳轉指令不會跳轉到方法體以外的字節碼指令上等
      >符號引用驗證:這一階段可以看作是對類自身以外的信息進行匹配性校驗,發生在虛擬機將符號引用轉化爲直接引用時。這個轉化將在第三階段--解析中發生。校驗內容包括:符號引用中通過字符串的全限定名是否能找到對應吃的類、在指定的類中是否存在符合字段描述符以及簡單名稱所描述的方法和字段。

準備階段

      這一階段是正式爲類變量分配內存並設置初始值階段。注意兩點:1、這個階段進行內存分配僅僅是爲類變量(static修飾的變量)而不包括實例變量,實例變量會在對象實例化時隨着對象一起在java堆上分配內存;2、這裏說的初始值通常說的是零值,真正的賦值操作是在初始化階段,當然這邊也有例外:public static final int value = 123這種情況下類字段的字段屬性表存在constantValue屬性,那麼在準備階段類變量的將被賦值。

解析階段

      虛擬機將常量池中的符號引用替換爲直接引用。符號引用和直接引用的區別:1、符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,引用的目標不一定在內存中已經加載。各種虛擬機實現的內存佈局可以多種形式但是符號引用必須一致的,這也是爲什麼符號引用的字面量明確定義在虛擬機規範class文件中的原因。解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符
     >類和接口解析:1、如果當前符號引用對應的類不是數組,虛擬機將符號引用代表的權限名傳遞該當前代碼所處的類的加載器,在加載過程中由於元數據驗證以及字節碼驗證的需要又會觸發其他相關類的加載動作;2、若爲數組且數組的元素類型爲對象,首先按照前一個步驟加載數組中的元素類型,接着虛擬機生成一個代表數組維度和元素的數組對象;3在前面無異常情況下解析完成進行符號引用驗證。
      >字段解析:1、對字段的class_index項中索引CONSTANT_class_info符號引用進行解析;2、如果這個字段對應類或接口本身包含該字段則直接返回該字段;3、否則,如果字段所屬類實現類接口,將會按照繼承關係從下往上遞歸搜索各個接口和它的父接口;4、否則,如果該字段所屬的類不是java.lang.object的話將會按照繼承關係從下往上遞歸搜索其父類中是否存在該字段;5、否則,查找失敗
      >類方法解析:1、也需要解析類方法表的class_index項中索引的方法所屬的類或接口的符號引用;2、在該方法對應的類中查找是否有簡單名稱和描述符都相匹配的方法;3、否則,在該類的父類中查找是否有簡單名稱和描述符都相匹配的方法;4、否則,在該類的接口列表遞歸的查找是否有簡單名稱和描述符都相匹配的方法;5、否則‘查找失敗。
      >接口方法解析:索引的對象是該方法處的接口或其父接口中

初始化階段

      初始化階段是類加載過程的最後一步,到了初始化階段,才真正開始執行類中定義的java程序代碼。初始化階段是執行類構造器<clinit>()方法的過程。需要注意以下幾點:1、<clinit>()方法是由編譯器自動收集類中所有類變量的賦值動作和靜態語句塊static{}塊中語句的合併產生;2、<clinit>()方法與類的構造器(實例構造器init())不同,它不需要顯示的調用父類構造器,虛擬機會保證在子類的<clinit>()方法執行之前,父類的<clinit>()方法執行完畢;3、<clinit>()方法對於類或接口不是必須的;4、接口和類一樣都會生成<clinit>()方法,但接口在<clinit>()方法執行不需要先執行父接口<clinit>()方法,只有當父接口中定義變量時使用;5、虛擬機會保證一個類<clinit>()方法在多線程環境中被正確的加鎖和同步。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章