1. 程式人生 > >Java類載入器和雙親委派模型.md

Java類載入器和雙親委派模型.md

0.類載入過程

一般來說,類載入分為3個過程,載入,連結和初始化。
1.載入階段,是Java將位元組碼資料從不同資料來源讀取到JVM中,並對映為JVM認可的Class物件,這裡的資料來源可能有Jar包,class檔案,甚至網路資料來源等。如果輸入資料不是ClassFile結構,則會丟擲ClassFormatError。

2.連結,這是核心步驟,將原始的類定義資訊平滑過渡到JVM執行過程中。細分為3個步驟

  • 驗證 JVM需要驗證位元組資訊是否符合Java虛擬機器規範,
  • 準備 建立類或介面中的靜態變數,並初始化靜態變數的初始值。這裡的初始化和第3步的初始化時有明顯區別的,側重點在於分配所需要的記憶體空間,不回去執行更近一部的JVM指令。
  • 解析 這一步會將常量池中的符號引用替換為直接引用。

3.初始化(顯示初始化)
這一步真正執行類初始化的程式碼邏輯,包括類靜態欄位和類定義靜態程式碼塊的初始化動作(需要額外呼叫putstatic等JVM指令),編譯器在編譯階段就會把這部分邏輯給整理好。注意,父類的初始化邏輯要先於子類執行。

補充一個 類和例項的初始化執行優先順序

初始化過程:

  1. 初始化父類中的靜態成員變數和靜態程式碼塊 ;
  2. 初始化子類中的靜態成員變數和靜態程式碼塊 ;
  3. 初始化父類的普通成員變數和程式碼塊,再執行父類的構造方法;
  4. 初始化子類的普通成員變數和程式碼塊,再執行子類的構造方法;

注意:靜態塊(包括靜態成員變數和靜態程式碼塊)在JVM中只初始化一次。

1.雙親委派模型的定義

當類載入器檢視載入某一個型別的時候,除非父載入器找不到當前型別,否則儘量將當前任務代理給父載入器去做。使用委派模型的目的是,避免重複載入Java型別。

#1.JVM 提供的3個預設的類載入器

1.1.BootStrap ClassLoader

稱為啟動類載入器,是Java類載入層次中最頂層的類載入器,負責載入JDK中的核心類庫,如:rt.jar、resources.jar、charsets.jar等,是通過查詢sun.boot.class.path這個系統屬性所得知的。它是一個超級公民,即便是開啟了Security Manager之後,JDK依然付給他了所有的載入許可權。

1.2 Extension ClassLoader

稱為擴充套件類載入器,負責載入Java的擴充套件類庫,預設載入JAVA_HOME/jre/lib/ext/目下的所有jar。這就是所有的extendsion機制,這個目錄也可以通過設定“java.ext.dirs”來覆蓋。

比如在執行jar是,通過這個引數指定它所依賴的jar:

java -jar xx.jar -Djava.ext.dirs=./lib/ com.Main

1.3 App ClassLoader

稱為應用類載入器,負責載入應用程式classpath目錄下的所有jar和class檔案。我們聽到的“系統類載入器”,通常就是指這個。你可以自定義自己的類載入器,使用如下引數替換系統類載入器:

java -Djava.system.class.loader=com.MyClassLoader Main

三個類載入器的關係圖:
在這裡插入圖片描述

2.類載入器的特徵

2.1 雙親委派模型

絕大多數類載入器都遵循雙親委派模型,但不是所有的。有時候,啟動類載入器載入的型別,可能需要載入使用者程式碼,使用者可以在標準API框架下,提供自己的實現。

2.2 可見性

子類載入器可以訪問父類載入器載入過的型別,反過來是不允許的。

2.3 單一性

子類載入器可以訪問父類載入器載入過的型別,所以父類載入器載入過的型別不會在子類載入器中重複載入。但是同級的類載入器是互不可見的,同一型別可以被多次載入,但是加載出的型別時不同的class。

JVM在判定兩個class是否相同時,不僅要判斷兩個類名是否相同,而且要判斷是否由同一個類載入器例項載入的。只有兩者同時滿足的情況下,JVM才認為這兩個class是相同的。就算兩個class是同一份class位元組碼,如果被兩個不同的ClassLoader例項所載入,JVM也會認為它們是兩個不同class。比如網路上的一個Java類org.classloader.simple.NetClassLoaderSimple,javac編譯之後生成位元組碼檔案NetClassLoaderSimple.class,ClassLoaderA和ClassLoaderB這兩個類載入器並讀取了NetClassLoaderSimple.class檔案,並分別定義出了java.lang.Class例項來表示這個類,對於JVM來說,它們是兩個不同的例項物件,但它們確實是同一份位元組碼檔案,如果試圖將這個Class例項生成具體的物件進行轉換時,就會拋執行時異常java.lang.ClassCaseException,提示這是兩個不同的型別

3.自定義類載入器的應用場景

3.1 實現程序內隔離

類載入器實際上用作不同的名稱空間,以提供類似容器,模組化的效果。例如,兩個模組依賴不同版本的類庫,如果被不同的類載入器載入,就可以互不干擾。

3.2 應用需要從不同的資料來源獲取類定義

應用需要從不同的資料來源獲取類定義資訊,如網路資料來源,而不是本地檔案系統。

3.3 動態生成或修改型別

這個過程需要自己操作位元組碼

自定義類載入器的載入過程
1.通過制定名稱,找到其二進位制實現,往往自定義載入器就在這裡實現“定製”部分。
2.建立Class,完成類載入過程。從二進位制到Class的轉化,不需要我們實現,通常依賴DefineClass