Java類加載器

虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把類加載階段中的 “通過一個(gè)類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流” 這個(gè)動(dòng)作放到虛擬機(jī)外部去實(shí)現(xiàn),以便讓應(yīng)用程序自己決定如何去獲取所需要的類,實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱為 “類加載器”

一. 類與類加載器

類加載器雖然只用于實(shí)現(xiàn)類的加載動(dòng)作,但是在Java程序中起到的作用遠(yuǎn)遠(yuǎn)不限于類加載階段。對(duì)于任何一個(gè)類,都需要由加載它的類加載器本身和這個(gè)類本身來確立其在Java虛擬機(jī)中的唯一性。比較兩個(gè)類是否 相等,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義。相等是指,代表類的Class對(duì)象的equals()、isAssignableFrom()isInstance()方法的返回結(jié)果,以及instanceof關(guān)鍵字做對(duì)象所屬關(guān)系判定。

二. 雙親委派模型

從java虛擬機(jī)的角度來講,只存在以下兩種不同的類加載器:

  1. 啟動(dòng)類加載器(Bootstrap ClassLoader),這個(gè)類加載器用C++實(shí)現(xiàn),是虛擬機(jī)自身的一部分。
  2. 所有其他類的加載器,這些類由Java實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部,并且全都繼承自抽象類java.lang.ClassLoader。

還可以細(xì)分為以下三種:

  1. 啟動(dòng)類加載器(Bootstrap ClassLoader) 此類加載器負(fù)責(zé)將存放在 <JAVA_HOME>\lib 目錄中的,或者被-Xbootclasspath參數(shù)所指定的路徑中的,并且是 虛擬機(jī)識(shí)別 的(僅按照文件名識(shí)別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會(huì)被加載)類庫加載到虛擬機(jī)內(nèi)存中。啟動(dòng)類加載器無法被Java程序直接引用,用戶在編寫自定義類加載器時(shí),如果需要把加載請求委派給引導(dǎo)類加載器,直接使用null代替即可。
  2. 擴(kuò)展類加載器(Extension ClassLoader) 這個(gè)類加載器是由ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實(shí)現(xiàn)的。它負(fù)責(zé)將<Java_Home>/lib/ext或者被java.ext.dir系統(tǒng)變量所指定路徑中的所有類庫加載到內(nèi)存中,開發(fā)者可以直接使用擴(kuò)展類加載器。
  3. 應(yīng)用程序類加載器(Application ClassLoader) 這個(gè)類加載器是由AppClassLoader(sun.misc.Launcher$AppClassLoader)實(shí)現(xiàn)的。由于這個(gè)類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般稱為 系統(tǒng)類加載器。它負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫,開發(fā)者可以直接使用這個(gè)類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個(gè)就是程序中 默認(rèn)的類加載器

類在加載器之間存在層次關(guān)系,也成為類加載器的 雙親委派模型(Parents Delgation Model),如下圖所示。該模型要求除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)有自己的父類加載器,這里類加載器之間的父子關(guān)系一般通過 組合(Composition) 關(guān)系來實(shí)現(xiàn),而不是通過繼承(Inheritance)的關(guān)系實(shí)現(xiàn)。

類加載器雙親委派模型

工作過程

如果一個(gè)類加載器收到了類加載的請求,它首先不會(huì)自己去嘗試加載,而是把這個(gè)請求委派給父類加載器,每一個(gè)層次的加載器都是如此,依次遞歸,因此 所有的加載請求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o法完成此加載請求(它搜索范圍中沒有找到所需類)時(shí),子加載器才會(huì)嘗試自己加載。

優(yōu)點(diǎn)

使用雙親委派模型來組織類加載器之間的關(guān)系,使得Java類隨著它的類加載器一起具備了一種 帶有優(yōu)先級(jí)的層次關(guān)系。例如類java.lang.Object,它存放再rt.jar中,無論哪個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。所以,雙親委派很好地解決了各個(gè)類加載器的 基礎(chǔ)類的統(tǒng)一 問題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載)。

同時(shí),也可以避免核心API被篡改。

缺陷

在雙親委派模型中,委派是單向的,子類加載器會(huì)委派給父類加載器,但是父類加載器無法委托給子類加載器,所以啟動(dòng)類加載器無法加載拓展類加載器加載的類,拓展類加載器也無法加載應(yīng)用程序類加載器加載的類。

實(shí)現(xiàn)

實(shí)現(xiàn)雙親委派的代碼都集中在java.lang.ClassLoaderloadClass()方法之中。如下代碼所示:

protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{
    //檢查請求的類是否加載過了
    Class c = findLoadedClass(name);
    if(c == null){
        try{
            if(parent != null){
                c = parent.loadClass(name,false);
            }else{
                c = findBootstrapClassOrNull(name);
            }
        }catch(ClassNotFoundException e){
            //如果拋出異常,說明父加載器無法完成加載請求
        }
        if(c == null){
            //在父類無法加載的時(shí)候
            //再調(diào)用本身的findClass方法來進(jìn)行類加載
            c = findClass(name);
        }
    }
    if(resolve){
        resolveClass(c);
    }
    return c;
}

步驟為:

  1. 首先,檢查請求的類是否加載過了,如果加載過了,就不需要再加載,直接返回。

  2. 如果此類沒有加載過,那么,再判斷一下是否有父加載器;如果有父加載器,則由父加載器加載(即調(diào)用parent.loadClass(name, false);)?;蛘呤钦{(diào)用bootstrap類加載器來加載。

  3. 如果父加載器及bootstrap類加載器都沒有找到指定的類,那么調(diào)用當(dāng)前類加載器的findClass方法來完成類加載

三. 初始類加載器和定義類加載器

在類加載的雙親委派模型下,類加載器首先會(huì)代理給它的父加載器來嘗試加載某個(gè)類,所以真正完成類的加載工作的類加載器和啟動(dòng)這個(gè)類加載過程的類加載器有可能不是同一個(gè)。真正完成類的加載工作是通過調(diào)用defineClass來實(shí)現(xiàn)的;而啟動(dòng)類的加載過程是通過調(diào)用loadClass來實(shí)現(xiàn)的。前者稱為一個(gè)類的定義加載器(defining loader),后者稱為初始加載器(initiating loader)。

JVM文檔是這樣說明的, 假設(shè)類或接口C的創(chuàng)建是因?yàn)轭惢蚪涌?code>D觸發(fā)的:

If D was defined by the bootstrap class loader, then the bootstrap class loader initiates loading of C (§5.3.1).
If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C (§5.3.2).

也就是,如果D啟動(dòng)類加載器定義,那么啟動(dòng)類加載器就是C初始加載器。如果D其他加載器定義,那么同一個(gè)加載器初始化C的加載,也就是C初始加載器。

在 Java 虛擬機(jī)判斷兩個(gè)類是否相同的時(shí)候,使用的是類的定義加載器(defining loader)。也就是說,哪個(gè)類加載器啟動(dòng)類的加載過程并不重要,重要的是最終定義這個(gè)類的加載器。

四、實(shí)現(xiàn)雙親委派的自定義類加載器

如前方法loadClass所示,該方法實(shí)現(xiàn)了雙親委派的邏輯,如果雙親類加載器沒有找到對(duì)應(yīng)的類,最后會(huì)使用findClass來加載類。所以實(shí)現(xiàn)滿足雙親委派的自定義類加載器只需要繼承ClassLoader類,并重寫findClass方法就行。

如下所示:

public class MyClassLoader extends ClassLoader {

    private String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    
    private byte[] loadByte(String name) throws Exception {
        name = name.replaceAll("\\.", "/");
        // 從文件系統(tǒng)中讀取
        FileInputStream fis = new FileInputStream(classPath + "/" + name
                + ".class");
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;

    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] bytes = loadByte(name);
            // 將一個(gè)字節(jié)數(shù)組轉(zhuǎn)換成 Class實(shí)例。
            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }
}

MyClass類如下:

public class MyClass {

    public void print() {
        System.out.println("MyClass, ClassLoader:" + getClass().getClassLoader().getClass().getName());
    }

}

執(zhí)行入口:

    public static void main(String[] args) throws Exception {
        MyClassLoader loader = new MyClassLoader("C:\\work\\leo\\leotest\\myclass\\target\\classes");
        Class<?> aClass = loader.loadClass("MyClass");
        Object o = aClass.newInstance();
        Method printMethod = aClass.getDeclaredMethod("print", null);
        printMethod.invoke(o);
    }

結(jié)果:

MyClass, ClassLoader:MyClassLoader
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 類加載器是 Java 語言的一個(gè)創(chuàng)新,也是 Java 語言流行的重要原因之一。它使得 Java 類可以被動(dòng)態(tài)加載到...
    CHSmile閱讀 1,668評(píng)論 0 12
  • ClassLoader的簡單介紹 Class的裝載大體上可以分為加載類、連接類和初始化三個(gè)階段,在這三個(gè)階段中,所...
    Y了個(gè)J閱讀 5,124評(píng)論 0 0
  • ClassLoader介紹 類加載器是負(fù)責(zé)加載類的一個(gè)對(duì)象,ClassLoader是一個(gè)抽象類。最常見的加載策略是...
    taj3991閱讀 2,491評(píng)論 1 4
  • 轉(zhuǎn)發(fā):本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布 ClassLoader翻譯過來就是類加載...
    尼爾君閱讀 615評(píng)論 0 1
  • java類加載器 Java類加載器(英語:Java Classloader)是Java運(yùn)行時(shí)環(huán)境(Java Run...
    禪與發(fā)現(xiàn)的樂趣閱讀 682評(píng)論 0 2

友情鏈接更多精彩內(nèi)容