(JVM)Java虛擬機器:(雙親委派模型)類載入器全解析

前言
- 瞭解 類載入器 有利用在類初始化時進行一些功能操作
- 本文全面講解類載入器,希望你們會喜歡。
在接下來的日子,我會推出一系列講解 JVM
的文章,具體如下;感興趣可持續關注 ofollow,noindex">Carson_Ho的安卓開發筆記

示意圖
目錄

目錄
1. 作用
Java
下面我會進行詳細講解。
1.1 實現類載入的功能
即實現 類載入過程中“載入”環節裡 “通過類的全限定名來獲取定義此類的二進位制位元組流” 的功能
具體請看我寫的文章: (JVM)Java虛擬機器:類載入的5個過程
1.2 確立 被載入類 在 Java
虛擬機器中 的 唯一性
- 確定 兩個類是否 相等 的依據: 是否由同一個類載入器載入
- 若 由同一個類載入器 載入,則這兩個類相等;
- 若 由不同的類載入器 載入,則這兩個類不相等。
即使兩個類來源於同一個 Class
檔案、被同一個虛擬機器載入,這兩個類都不相等
- 在實際使用中,是通過下面方法的返回結果(
Boolean
值)進行判斷:-
Class
物件的equals()
方法 -
Class
物件的isAssignableFrom()
方法 -
Class
物件的isInstance()
方法
-
當然也會使用instanceof關鍵字做物件所屬關係判定等情況
-
例項說明
下面我將舉個例子來說明:
public class Test { // 自定義一個類載入器:myLoader // 作用:可載入與自己在同一路徑下的Class檔案 static ClassLoader myLoader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { if (!name.equals("com.carson.Test")) return super.loadClass(name); try { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(fileName); if (is == null) { return super.loadClass(fileName); } byte[] b = new byte[is.available()]; is.read(b); return defineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(name); } } }; public static void main(String[] args) throws Exception { Object obj = myLoader.loadClass("com.carson.Test"); // 1. 使用該自定義類載入器載入一個名為com.carson.Test的類 // 2. 例項化該物件 System.out.println(obj); // 輸出該物件的類 ->>第一行結果分析 System.out.println(obj instanceof com.carson.Test); // 判斷該物件是否屬於com.carson.Test類 ->>第二行結果分析 } } <-- 輸出結果 --> class com.carson.Test false // 第一行結果分析 // obj物件確實是com.carson.Test類例項化出來的物件 // 第二行結果分析 // obj物件與類com.huachao.Test做所屬型別檢查時卻返回了false // 原因:虛擬機器中存在了兩個Test類(1 & 2):1是由系統應用程式類載入器載入的,2是由我們自定義的類載入器載入 // 雖然都是來自同一個class檔案,但由於由不同類載入器載入,所以依然是兩個獨立的類 // 做物件所屬型別檢查結果自然為false。
2. 類載入器的型別
- 類載入器的型別數量分別從
Java
虛擬機器 &Java
開發者的角度來看,如下圖

示意圖
- 下面主要講解從
Java
開發者角度看的類載入器,即講解:- 啟動類載入器
- 擴充套件類載入器
- 應用程式類載入器
2.1 啟動類載入器(Bootstrap ClassLoader)
-
作用
負責載入以下類:
<JAVA_HOME>\lib -Xbootclasspath
僅按檔名識別,如: rt.jar
,名字不符合的類庫即使放在lib目錄中也不會被載入
- 特別注意
- 啟動類載入器 無法 被
Java
程式直接引用 - 使用者在編寫自定義類載入器時,若需把 載入請求 委派 給 引導類載入器,直接使用
null
代替即可,如java.lang.ClassLoader.getClassLoader()
方法所示:
- 啟動類載入器 無法 被
@CallerSensitive public ClassLoader getClassLoader() { ClassLoader cl = getClassLoader0(); if (cl == null) return null; SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass()); } return cl; }
2.2 擴充套件類載入器(Extension ClassLoader)
-
作用:
負責載入以下類:
<JAVA_HOME>\lib\ext java.ext.dirs
-
特別注意
sum.misc.Launcher$ExtClassLoader
2.3 應用程式類載入器(Application ClassLoader)
-
作用:
負責載入 使用者類路徑(
ClassPath
)上所指定的類庫 -
特別注意
- 也稱為系統類載入器,因為該類載入器是
ClassLoader
中的getSystemClassLoader()
方法的返回值 - 由
sum.misc.Launcher$AppClassLoader
類實現 - 開發者可以直接使用該類載入器
- 若開發者 沒 自定義類載入器,程式預設使用該類載入器
- 也稱為系統類載入器,因為該類載入器是
- 各種類載入器的使用並不是孤立的,而是相互配合使用
- 在
Java
虛擬機器中,各種類載入器 配合使用 的 模型(關係)是 雙親委派模型
下面我將詳細講解。
3. 雙親委派模型
3.1 模型說明

示意圖
3.2 工作流程講解
java.lang.ClassLoader的loadClass()
@Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> c = findLoadedClass(name); // 檢查需要載入的類是否已經被載入過 if (c == null) { try { // 若沒有載入,則呼叫父載入器的loadClass()方法 if (parent != null) { c = parent.loadClass(name, false); }else{ // 若父類載入器為空,則預設使用啟動類載入器作為父載入器 c=findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 若父類載入器載入失敗會丟擲ClassNotFoundException, //說明父類載入器無法完成載入請求 } if(c==null){ // 在父類載入器無法載入時 // 再呼叫本身的findClass方法進行類載入 c=findClass(name); } } if(resolve){ resolveClass(c); } return c; }
步驟總結:若一個類載入器收到了類載入請求
- 把 該類載入請求 委派給 父類載入器 去完成,而不會自己去載入該類
每層的類載入器都是如此,因此所有的載入請求最終都應傳送到頂層的啟動類載入器中
- 只有當 父類載入器 反饋 自己無法完成該載入請求(它的搜尋範圍中沒有找到所需的類)時,子載入器才會自己去載入
3.3 優點
Java
類隨著它的類載入器一起具備了一種帶優先順序的層次關係
- 如:類
java.lang.Object
(存放在rt.jar
中)在載入過程中,無論哪一個類載入器要載入這個類,最終需委派給模型頂端的啟動類載入器進行載入,因此Object
類在程式的各種類載入器環境中都是同一個類。 - 若沒有使用雙親委派模型(即由各個類載入器自行去載入)、使用者編寫了一個
java.lang.Object
的類(放在ClassPath
中),那系統中將出現多個不同的Object
類,Java體系中最基礎的行為就無法保證
在講完系統的類載入器後,下面我將講解如何根據需求自定義類載入器。
4. 自定義類載入器
主要是通過繼承自ClassLoader類 從而自定義一個類載入器
MyClassLoader.java
// 繼承自ClassLoader類 public class MyClassLoader extends ClassLoader { // 類載入器的名稱 private String name; // 類存放的路徑 private String classpath = "E:/"; MyClassLoader(String name) { this.name = name; } MyClassLoader(ClassLoader parent, String name) { super(parent); this.name = name; } @Override public Class<?> findClass(String name) { byte[] data = loadClassData(name); return this.defineClass(name, data, 0, data.length); } public byte[] loadClassData(String name) { try { name = name.replace(".", "//"); System.out.println(name); FileInputStream is = new FileInputStream(new File(classpath + name + ".class")); byte[] data = new byte[is.available()]; is.read(data); is.close(); return data; } catch (Exception e) { e.printStackTrace(); } return null; } }
下面我將用一個例項來說明如何自定義類載入器 & 使用。
步驟1:自定義類載入器 MyClassLoader
MyClassLoader.java
// 繼承自ClassLoader類 public class MyClassLoader extends ClassLoader { // 類載入器的名稱 private String name; // 類存放的路徑 private String classpath = "E:/"; MyClassLoader(String name) { this.name = name; } MyClassLoader(ClassLoader parent, String name) { super(parent); this.name = name; } @Override public Class<?> findClass(String name) { byte[] data = loadClassData(name); return this.defineClass(name, data, 0, data.length); } public byte[] loadClassData(String name) { try { name = name.replace(".", "//"); System.out.println(name); FileInputStream is = new FileInputStream(new File(classpath + name + ".class")); byte[] data = new byte[is.available()]; is.read(data); is.close(); return data; } catch (Exception e) { e.printStackTrace(); } return null; } }
步驟2:定義待載入的類
TestObject.java
public class TestObject { public void print() { System.out.println("hello DiyClassLoader"); } }
步驟3:定義測試類
Test.java
public class Test { public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException { MyClassLoader cl = new MyClassLoader("myClassLoader"); // 步驟1:建立自定義類載入器物件 Class<?> clazz = cl.loadClass("com.carson.TestObject"); // 步驟2:載入定義的測試類:myClassLoader類 TestObject test= (TestObject) clazz.newInstance(); // 步驟3:獲得該類的物件 test.print(); // 輸出 } } // 輸出結果 hello DiyClassLoader
4. 總結
- 本文全面講解類載入器
- 在接下來的日子,我會推出一系列講解
JVM
的文章,具體如下;感興趣可持續關注 Carson_Ho的安卓開發筆記

示意圖