類加載機制與雙親委派

虛擬機將描述類的數據從字節碼文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的 Java 類型,這就是虛擬機的類加載機制。類加載器負責將字節碼文件中的二進制數據加載到內存中,在運行時數據區的方法區內創建 Class 對象,這也是類加載過程的最終產物。類型的加載、鏈接、初始化的過程都是在程序運行時完成的。

什麼是類加載器?

虛擬機設計團隊把類加載階段中的 通過一個類的全限定名來獲取描述此類的二進制字節流 這個動作放到 Java 虛擬機外部去實現,以便讓應用程序自己決定如何獲取所需要的類,實現這個動作的代碼模塊稱為 類加載器 。類加載器雖然只用於實現類的加載動作,但它在 Java 程序中起到的作用卻遠遠不限於類加載階段。對於任意一個類,都需要由加載它的類加載器和這個類本身一同確立在 Java 虛擬機中的唯一性。

類的生命週期

類從被加載到虛擬機內存中開始,到卸出內存為止,它的整個生命週期包括: 加載、驗證、準備、解析、初始化、使用、卸載 7 個階段。其中 驗證、準備、解析 3 個部分統稱為鏈接,這七個階段的發生順序如圖所示:

類加載機制與雙親委派

加載階段

加載是類加載過程的一個階段。在加載階段,虛擬機需要完成以下三件事情:

<code>

java

.lang

.Class

/<code>

驗證階段

驗證是連接階段的第一步,這一階段的目的是為了確保字節碼文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。為什麼會有這個階段?因為字節碼文件並不一定要求用 Java 源碼編譯而來,可以使用任何途徑產生,甚至包括用十六進制編輯器直接編寫來產生字節碼文件。驗證階段大致會完成下面 4 個階段的檢驗動作:

  1. 文件格式驗證。這一階段要驗證字節流是否符合字節碼文件格式的規範,並且能被當前版本的虛擬機處理。
  2. 元數據驗證。這一階段是對字節碼描述的信息進行語義分析,以保證其描述的信息符合 Java 語言規範的要求。
  3. 字節碼驗證。這一階段主要目的是通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的。
  4. 符號引用驗證。這一階段的校驗發生在虛擬機將符號引用轉化為直接引用的時候,這個轉化動作將在連接的第三個階段-解析階段中發生。符號引用驗證可以看做是對類自身以外(常量池中的各種符號引用)的信息進行匹配性校驗。

準備階段

準備階段是正式為類變量分配內存並設置類變量初始值的階段,這些變量所使用的的內存都將在方法區中進行分配。首先,這時候進行內存分配的僅包括類變量(被 static 修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在 Java 堆中。下圖是 Java 基本數據類型的初始值:

類加載機制與雙親委派

解析階段

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

初始化

類初始化階段是類加載過程的最後一步,前面的類加載過程中,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其餘動作完全由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的 Java 程序代碼。

什麼是雙親委派

從 Java 虛擬機的角度來說,只存在兩種不同的類加載器。

<code>

Bootstrap

ClassLoader

java

.lang

.ClassLoader

/<code>

從 Java 開發人員的角度來看,大部分 Java 程序一般會使用到以下三種系統提供的類加載器:

  1. 啟動類加載器 Bootstrap ClassLoader 。負責加載 \lib 目錄中並且能被虛擬機識別的類庫到虛擬機內存中,如果名稱不符合的類庫即使放在 lib 目錄中也不會被加載。該類加載器無法被 Java 程序直接引用
  2. 擴展類加載器 Extension ClassLoader 。該加載器主要是負責加載 \lib\ext ,該加載器可以被開發者直接使用
  3. 應用程序類加載器 Application ClassLoader 。該類加載器也稱為系統類加載器,它負責加載用戶類路徑 CLASS_PATH 上所指定的類庫,開發者可以直接使用該類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器

下圖展示了類加載器之間的這種層次關係。這種模型被稱為類加載器的 雙親委派模型

類加載機制與雙親委派

雙親委派模型的工作過程是:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求都最終應該傳送到頂層的啟動類加載器中,只有當父類加載器反饋自己無法完成這個加載請求(它的搜索範圍中沒有找到所需的類)時,子類加載器才會嘗試自己去加載。

為什麼這麼設計?

使用 雙親委派模型 來組織類加載器之間的關係,有一個顯而易見的好處就是 Java 類隨著它的類加載器一起具備了一種帶有優先級的層次關係。例如類 java.lang.Object ,它存放在 rt.jar 之中,無論哪一個類加載器要加載這個類,最終都是委派給處於模型最頂端的啟動類加載器進行加載,因此 Object 類在程序的各種類加載器環境中都是同一個類。相反,如果沒有使用雙親委派模型,由各個類自行去加載的話,如果用戶自己編寫了一個 java.lang.Object 的類,並放在程序的 CLASS_PATH 中,那系統中將會出現多個不同的 Object 類,Java 體系同最基礎的行為也就無法保證,應用程序也會變得一片混亂。


分享到:


相關文章: