《深刻理解 Java 虛擬機》讀書筆記:虛擬機類加載機制

正文

虛擬機把描述類的數據從 Class 文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的 Java 類型,這就是虛擬機的類加載機制。java

1、類加載的時機

一、類的生命週期

加載 -> 鏈接(驗證、準備、解析) -> 初始化 -> 使用 -> 卸載程序員

加載、驗證、準備、初始化和卸載這 5 個階段的順序是肯定的,類的加載過程必須按這種順序循序漸進地開始。解析階段則不必定,它在某些狀況能夠在初始化以後再開始,這是爲了支持 Java 語言的運行時綁定(也稱動態綁定或晚期綁定)。安全

這些階段一般是互相交叉地混合式進行,一般會在一個階段執行的過程當中調用、激活另一個階段。數據結構

二、類的初始化時機

  • 遇到 new(實例化對象)、getstatic(讀取類的靜態字段)、putstatic(設置類的靜態字段)、invokestatic(調用類的靜態方法) 這 4 條字節碼指令時。
  • 使用 java.lang.reflect 包的方法對類進行反射調用時。
  • 初始化一個類的子類時。
  • 虛擬機啓動時,會先初始化主類(main 方法所在的類)。
  • 使用 java.lang.invoke.MethodHandle 獲取類的方法句柄時。

2、類加載的過程

一、加載

「加載」是「類加載」過程的一個階段,在加載階段,虛擬機須要完成 3 件事:spa

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

二、驗證

這一階段的目的是爲了確保 Class 文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。驗證階段大體包含 4 個階段的檢驗動做。設計

(1)文件格式驗證

驗證字節流是否符合 Class 文件格式的規範,而且能被當前版本的虛擬機處理。指針

驗證內容:對象

  • 是否以魔數 0xCAFEBABE 開頭。
  • 主、次版本號是否在當前虛擬機處理範圍以內。
  • 常量池的常量中是否有不被支持的常量類型。
  • ...

主要目的是保證輸入的字節流能正確解析並存儲於方法區內,格式上符合描述一個 Java 類型信息的要求。繼承

該階段的驗證是基於字節流進行的,只有驗證經過了,字節流纔會進入內存的方法區中進行存儲。因此後面 3 個驗證階段都是基於方法區的存儲結構進行的。接口

(2)元數據驗證

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

驗證內容:

  • 這個類是否有父類(除 java.lang.Object 外,全部類都應當有父類)。
  • 這個類的父類是否繼承了不容許被繼承的類(被 final 修飾的類)。
  • 若是這個類不是抽象類,是否實現了其父類或接口中要求實現的全部方法。
  • ...

主要目的是對類的元數據信息進行語義校驗,保證不存在不符合 Java 語言規範的元數據信息。

(3)字節碼驗證

對類的方法體進行檢驗分析,保證類的方法在運行時不會作出危害虛擬機安全的事件。

驗證內容:

  • 保證任意時刻操做數棧的數據類型與指令代碼序列都能配合工做。
  • 保證跳轉指令不會跳轉到方法體之外的字節碼指令上。
  • 保證方法體中的類型轉換是有效的。
  • ...

主要目的是經過數據流和控制流分析,肯定程序語義是合法的、符合邏輯的。

(4) 符號引用驗證

對類自身之外(常量池中的各類符合引用)的信息進行匹配性校驗。這一階段發生在虛擬機將符號引用轉化爲直接引用時。

驗證內容:

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

主要目的是確保解析動做能正常執行。

三、準備

準備階段是爲類變量分配內存並設置初始值的階段。

有兩點須要強調一下:

  • 進行內存分配的僅包括類變量,而不包括實例變量。
  • 初始值一般是數據類型的零值。

四、解析

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

  • 符號引用:以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時能無歧義地定位到目標便可。
  • 直接引用:能夠是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。

解析動做主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符 7 類符號引用進行,分別對應常量池的 CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info、CONSTANT_MethodType_info、CONSTANT_MethodHandle_info 和 CONSTANT_InvokeDynamic_info 7 種常量類型。

五、初始化

初始化階段是根據程序員的主觀計劃去初始化類變量和其餘資源的階段。或者從另外一個角度表達,初始化階段是執行類構造器 clinit() 方法的過程。

到了初始化階段,才真正開始執行類中定義的 Java 程序代碼(或者說字節碼)。

3、類加載器

虛擬機設計團隊把類加載階段中的「經過類的全限定名獲取定義此類的二進制字節流」這個動做放到 Java 虛擬機外部去實現,以便讓應用程序本身決定如何去獲取所需的類。實現這個動做的代碼模塊稱爲「類加載器」。

一、類與類加載器

對於任意一個類,都須要由加載它的類加載器(每個類加載器都有一個獨立的類名稱空間)和這個類自己,一同確立其在 Java 虛擬機中的惟一性。

換句話說,比較兩個類是否「相等」,只有在這兩個類是由同一個類加載器加載的前提下才有意義。

二、類加載器分類

從 Java 虛擬機角度講,只存在兩種不一樣的類加載器:

  • 啓動類加載器:使用 C++ 語言實現,是虛擬機自身的一部分。
  • 其餘類加載器:由 Java 語言實現,獨立於虛擬機外部。

從 Java 開發人員角度講,可分爲三種類加載器:

  • 啓動類加載器(Bootstrap ClassLoader):負責將存放在 <JAVA_HOME>\lib 目錄的,或者 -Xbootclasspath 參數所指定路徑中的,能被虛擬機識別的類庫加載到虛擬機內存中。
    啓動類加載器沒法被 Java 程序直接引用。
  • 擴展類加載器(Extension ClassLoader):由 sun.misc.Launcher$ExtClassLoader 實現,負責加載 <JAVA_HOME>\lib\ext 目錄中的,或者 java.ext.dirs 系統變量所指定路徑中的全部類庫。
    開發者可直接使用擴展類加載器。
  • 應用程序類加載器(Application ClassLoader):由 sun.misc.Launcher$AppClassLoader 實現,負責加載用戶類路徑上所指定的類庫。
    開發者可直接使用應用程序類加載器。

三、雙親委派模型

若是一個類加載器收到類加載的請求,它會先把這個請求委派給父加載器去完成,而不會本身去嘗試加載這個類。只有父加載器沒法完成這個加載請求時,子加載器纔會嘗試本身去加載。

相關文章
相關標籤/搜索