(JVM)Java虛擬機:類加載器詳解(雙親委派模型)

目錄

在這裏插入圖片描述

一、作用

  • 實現類加載的功能
  • 確定被加載類 在 Java虛擬機中 的 唯一性

下面我會進行詳細講解。

1、實現類加載的功能

即實現 類加載過程中“加載”環節裏 “通過類的全限定名來獲取定義此類的二進制字節流” 的功能

具體請看:(JVM)Java虛擬機:類的生命週期(類加載的5個過程)詳解

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。

二、類加載器的類型

  • 類加載器的類型數量分別從 Java虛擬機 & Java開發者的角度來看,如下圖
    在這裏插入圖片描述
  • 下面主要講解從 Java 開發者角度看的類加載器,即講解:
  • 啓動類加載器
  • 擴展類加載器
  • 應用程序類加載器
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、擴展類加載器(Extension ClassLoader)
  • 作用:

負責加載以下類:

  • <JAVA_HOME>\lib\ext目錄中的類庫
  • 被java.ext.dirs系統變量所指定的路徑中的所有類庫
  • 特別注意
  • sum.misc.Launcher$ExtClassLoader類實現
  • 開發者可以直接使用擴展類加載器
3、應用程序類加載器(Application ClassLoader)
  • 作用:

負責加載 用戶類路徑(ClassPath)上所指定的類庫

  • 特別注意
  • 也稱爲系統類加載器,因爲該類加載器是ClassLoader中的getSystemClassLoader()方法的返回值
  • 由sum.misc.Launcher$AppClassLoader類實現
  • 開發者可以直接使用該類加載器
  • 若開發者 沒 自定義類加載器,程序默認使用該類加載器
  • 各種類加載器的使用並不是孤立的,而是相互配合使用
  • 在Java虛擬機中,各種類加載器 配合使用 的 模型(關係)是 雙親委派模型

下面我將詳細講解。

三、雙親委派模型

1、模型說明

在這裏插入圖片描述

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、優點

Java類隨着它的類加載器一起具備了一種帶優先級的層次關係

  • 如:類 java.lang.Object(存放在rt.jar中)在加載過程中,無論哪一個類加載器要加載這個類,最終需委派給模型頂端的啓動類加載器進行加載,因此Object類在程序的各種類加載器環境中都是同一個類。
  • 若沒有使用雙親委派模型(即由各個類加載器自行去加載)、用戶編寫了一個java.lang.Object的類(放在ClassPath中),那系統中將出現多個不同的Object類,Java體系中最基礎的行爲就無法保證

在講完系統的類加載器後,下面我將講解如何根據需求自定義類加載器。

四、自定義類加載器

主要是通過繼承自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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章