ClassLoader解析——Java篇

我們知道,Java程序的啟動,其實是依賴于我們編譯之后的.class文件,那么JVM是如何讀取.class文件的?這一切都離不開ClassLoader這個類,ClassLoader可以將Java字節碼加載入JVM當中,供后面使用。

前言

一個Java程序的啟動流程,大致分為:編譯->加載->鏈接->初始化->運行。

  • 編譯,也就是我們常見的將.java文件轉換為.class文件的過程。一般我們可以通過javac命令進行編譯,或者在ide中我們build一下就進行了編譯
  • 加載,也就是這個環節利用了ClassLoader,將.class文件加載進JVM中
  • 鏈接,包括驗證、準備和解析等幾個步驟
  • 初始化,主要是執行了類中的靜態代碼
  • 運行,顧名思義,通過JVM虛擬機在對應平臺上運行程序代碼

三個類加載器

我們知道Java程序的啟動,需要類加載器ClassLoader來將java字節碼載入JVM,那么ClassLoader又是從何處開始加載呢?

概述

在Java中,系統自帶了三個類記載器:

  1. BootStrap ClassLoader 最頂層的類加載器,Java會最先啟動這個類加載器,加載核心類庫,加載%JRE_HOME%\lib目錄下的jar包和class文件等,其中就包括了Java的基礎類,如String,IO等等
  2. Extention ClassLoader 第二層的類加載器,加載拓展類庫,會加載%JRE_HOME%\lib\ext目錄下的jar包和class文件等,其中包括了各地的時間語言包等。
  3. App ClassLoader 第三層的類加載庫,加載當前應用目錄下的jar包和class文件。

加載順序

  1. BootStrap ClassLoader
    剛才說了,BootStrap ClassLoader是最頂層的類加載器,負責加載核心類庫,自然也就是最先加載的ClassLoader了,然而我們卻沒辦法在Java中找到對應的類。黑人問號臉???

剛才也說了,String,File等基礎類都是由他在最開始加載的。我們嘗試調用以下代碼:

System.out.println(String.class.getClassLoader());

結果卻是null???

通過查閱資料可以得知,BootStrap ClassLoaer其實是C/C++實現的,是它本身是虛擬機的一部分,所以我們在Java代碼中并沒有辦法找到他。所以自然也就是null

  1. Extention ClassLoader&AppClassLoader
    BootStrap ClassLoader不同,Extention ClassLoader&AppClassLoader我們可以在代碼中找到,他們都是Launcher類的內部類:
public class Launcher {
    public Launcher() {
        //省略大量代碼
        Launcher.ExtClassLoader var1;
        //加載ExtClassLoader,沒有傳入參數
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
        //加載AppClassLoader,傳入ExtClassLoader作為參數,并將AppClassLoader設為Launcher的ClassLoader
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        ……
    }
    
    static class AppClassLoader extends URLClassLoader {
        //省略
        AppClassLoader(URL[] var1, ClassLoader var2) {
            super(var1, var2, Launcher.factory);
        }
    }
    
    static class ExtClassLoader extends URLClassLoader {
        //省略
        public ExtClassLoader(File[] var1) throws IOException {
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
        }
    }
}

查閱資料可以得知,Launcher是由JVM的入口應用,由虛擬機啟動時通過JNI啟動該類。

我們可以看到Launcher的構造函數中,依次加載了ExtClassLoaderAppClassLoader

所以我們可以總結出:JVM會首先加載BootStrap ClassLoader,接著加載ExtClassLoader,然后才加載AppClassLoader。

工作原理——雙親委托

我們可以先看看ClassLoader的構造函數:

public abstract class ClassLoader {
    private final ClassLoader parent;
    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        ……//省略一堆
    }
}

可以看到,在構造函數中,傳入了一個ClassLoader作為parent。
同時ClassLoader有一個getParent的方法,可以通過這個方法獲取當前ClassLoader的Parent,那么AppClassLoader和ExtClassLoader的parent是什么鬼呢,我們實驗一下:

System.out.println(Test.class.getClassLoader());
System.out.println(Test.class.getClassLoader().getParent());
System.out.println(Test.class.getClassLoader().getParent().getParent());

打印結果:

sun.misc.Launcher$AppClassLoader@28d93b30
sun.misc.Launcher$ExtClassLoader@14ae5a5
null

看到這樣的輸出結果,我們難免會有些疑惑:

  1. AppClassLoader的parent是ExtClassLoader?
  2. ExtClassLoader的parent是null?
  3. BootStrapClassLoader呢???

為了解開這些疑惑,我們看一下AppClassLoader&ExtClassLoader的構造函數:

public class Launcher {
    public Launcher() {
        //省略大量代碼
        Launcher.ExtClassLoader var1;
        //加載ExtClassLoader,沒有傳入參數
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
        //加載AppClassLoader,傳入ExtClassLoader作為參數,并將AppClassLoader設為Launcher的ClassLoader
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        ……
    }
    
    static class AppClassLoader extends URLClassLoader {
        //省略
        AppClassLoader(URL[] var1, ClassLoader var2) {
            // var2對應傳入了ExtClassLoader作為parent
            super(var1, var2, Launcher.factory);
        }
    }
    
    static class ExtClassLoader extends URLClassLoader {
        //省略
        public ExtClassLoader(File[] var1) throws IOException {
            // 傳入null作為parent
            super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
        }
    }
}

可以看到,Launcher在構造時會先創建ExtClassLoader然后將創建的ExtClassLoader做為參數構建AppClassLoader。而ExtClassLoader創建時會傳入null作為parent,AppClassLoader會傳入剛構建的ExtClassLoader作為parent。

這也就印證了我們剛才的輸出結果。但是parent的意義有是什么呢?

加載過程

接下來我們需要先看一下ClassLoader是如何加載一個Class的:
在Java會通過ClassLoader的loadClass方法來加載一個類,我們先看一下這個方法:

public abstract class ClassLoader {
    /**
     *   使用特定的二進制名來加載類
     *   默認會執行如下的順序:
     *   1. 通過findLoadedClass()判斷這個類時候已經加載過了
     *   2. 通過parent.loadClass()嘗試讓parent加載這個class
     *      如果parent是null,那么調用虛擬機創建的ClassLoaer
     *   3. 通過findClass()加載類,最終defineClass方法將jar文件轉為Class
     *   
     *   如果通過上述步驟可以加載出對應的class,那么調用resolveClass(Class 處理結果。
     *   子類應該重寫findClass()方法而不是這個方法
     */
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        //省略部分代碼
        synchronized (getClassLoadingLock(name)) {
            // 首先檢查這個類是否已經被加載過
            Class<?> c = findLoadedClass(name);
            //如果沒有被加載過
            if (c == null) {
                    // 如果parent不為空
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 如果parent為空,那么調用Bootstrap classLoader加載這個類
                        c = findBootstrapClassOrNull(name);
                    }

                if (c == null) {
                    // 如果仍然為空,那么調用findClass
                    c = findClass(name);
                }
            }
            if (resolve) {
                // 處理結果
                resolveClass(c);
            }
            return c;
        }
    }
}

通過注釋我們可以清楚的看到這個方法的主要邏輯:

  1. 通過findLoadedClass()判斷這個類時候已經加載過了
  2. 通過parent.loadClass()嘗試讓parent加載這個class
    如果parent是null,那么調用虛擬機創建的BootStrapClassLoaer
  3. 通過findClass()(最終轉到defineClass方法)加載類,將jar文件轉為Class

TODO:= =本來打算畫個圖。??墒窃趺串嫸疾缓弦狻?。

可以看到,ClassLoader會先委托parent進行加載,如果加載不出來才會自己進行加載,所以Classloaderparent其實是一個層級關系。

這也正是剛才說的雙親委托機制:先委托parent進行加載,加載不出來再自己加載。
為什么要選擇這種機制呢?試想一下如果現在我也實現了一個String類(包名也一樣),這樣JVM中是不是就會有兩種String呢?這樣不就亂套了嗎,而且也出現了安全問題
采用雙親委托機制就很好的解決了這種情況,我們永遠沒有辦法加載一個自定義的String類(包名也相同)。

總結

  1. 首先JVM會依次加載BootStrapClassLoader(C/C++),ExtClassLoaderAppClassLoader。

  2. AppClassLoaderparentExtClassLoader,而ExtClassLoaderparent可以認為是BootStrapClassLoader(因為當parent為null時,會調用BootStrapClassLoader來加載)

  3. ClasLoader加載時采用雙親委托機制,會優先委托parent加載,如果null,再自己加載

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容