JVM學(xué)習(xí)(二)類加載器

目錄


一裸弦、類加載器

還記得類加載機制嗎?類加載機制的各階段是加載作喘、連接(驗證理疙、準備、解析)泞坦、初始化窖贤、使用、卸載贰锁≡呶啵可參考上篇文章:JVM學(xué)習(xí)(一):Java類的加載機制 里有詳細說明。

1. 什么是類加載器豌熄?

把類加載階段中的"通過一個類的全限定名來獲取描述此類的二進制字節(jié)流"這個動作放到Java虛擬機外部去實現(xiàn)授嘀,以便讓應(yīng)用程序自己決定如何去獲取所需要的類。實現(xiàn)這個動作的代碼模塊稱為“類加載器”锣险。

2. 類與類加載器

類加載器雖然只用于實現(xiàn)類的加載動作粤攒,但它在Java程序中祈禱的作用卻遠遠不限于類加載階段。
對于任意一個類囱持,都需要由加載它的類加載器和這個類本身一同確立啊Java虛擬機中的唯一性夯接。每一個類加載器都擁有一個獨立的類名稱空間。
比較兩個類是否“相等“纷妆,只有在這兩個類是由同一個類加載器加載的前提下才由意義盔几;否則,即使兩個類來源于同一個Class文件掩幢,被同一個虛擬機加載逊拍,只要加載他們的類加載器不同,那么這兩個類就必定不相等际邻。
(這里指的“相等”芯丧,包括代表類的Class對象的equals()方法、isAssignableFrom()方法世曾、isInstance()方法的返回結(jié)果缨恒,也包括關(guān)鍵字instanceof做對象所屬關(guān)系判定情況。)

  • 代碼演示:
    public class ClassLoaderTest {
        public static void main(String[] args) throws Exception {
            ClassLoader loader = new ClassLoader() {
                @Override
                public Class<?> loadClass(String name) throws ClassNotFoundException {
                    try {
                        String fileName = name.lastIndexOf("." + 1) + ".class";
                        InputStream inputStream = this.getClass().getResourceAsStream(fileName);
                        if (inputStream == null) {
                            return super.loadClass(name);
                        }
                        byte[] bytes = new byte[inputStream.available()];
                        inputStream.read(bytes);
                        return defineClass(name, bytes, 0, bytes.length);
                    } catch (IOException e) {
                        e.printStackTrace();
                        throw new ClassNotFoundException(name);
                    }
                }
            };
    
            Object obj = loader.loadClass("com.jx.Test1").newInstance();
            System.out.println(obj.getClass()); // 打印類名稱
            System.out.println(obj instanceof com.jx.Test1); // 打印 比較obj對象是否是com.jx.Test1類
        }
    }
    
  • 運行結(jié)果:
      class com.jx.Test1
      false
    

從示例代碼Object obj = loader.loadClass("com.jx.Test1").newInstance(); System.out.println(obj.getClass());打印結(jié)果是com.jx.Test1轮听,說明通過自定義的類加載器 加載并實例的對象確實是Tes1的類骗露;
但代碼System.out.println(obj instanceof com.jx.Test1);運行輸出的結(jié)果是false,這是因為在JVM虛擬機中存在另外一個類加載器加載了血巍。
雖然都是來自同一個Class文件萧锉,但因為是兩個獨立的類加載器加載出來的類,在做對象所屬類型檢測時結(jié)果是false述寡。

二柿隙、類加載器分類

類加載器
  • 類加載器可以分為:

    • 啟動類加載器(Bootstrap ClassLoader)
    • 擴展類加載器(Extension ClassLoader)
    • 應(yīng)用程序類加載器(Application ClassLoader)
    • 自定義類加載器(USer ClassLoader)
  • 他們的關(guān)系是:
    自定義類加載器的父類是應(yīng)用程序類加載器叶洞;
    應(yīng)用程序類加載器的父類是擴展類加載器;
    啟動類加載器嚴格意義上不是擴展類加載器的父類禀崖,抽象維度可以理解為父類衩辟。

1. 啟動類加載器(Bootstrap ClassLoader)

啟動類加載器(Bootstrap ClassLoader) 是最頂層的類加載器,主要加載核心類庫帆焕。

  • 加載路徑\jdk\jre\lib下的rt.jar、resource.jar不恭、charsets.jar和class等叶雹。
  • 啟動類架子啊其是無法被Java程序直接引用的。
    Bootstrap ClassLoader不繼承自ClassLoader换吧,因為它不是一個普通的Java類折晦,底層是由C++編寫嵌入到JVM內(nèi)核中;
    當JVM啟動后 Bootstrap ClassLoader也隨著啟動沾瓦,賦值加載完核心類庫后满着,并構(gòu)造Extension ClassLoader和Application ClassLoader。
    如圖:
    啟動類加載器加載路徑

    另外贯莺,可以通過啟動JVM時指定-Xbootclasspath路徑來改變Bootstrap ClassLoader的加載目錄风喇。

2. 擴展類加載器(Extension ClassLoader)

擴展類加載器(Extension ClassLoader):這個類加載器由sun.misc.Luancher&ExtClassLoader實現(xiàn)。

  • 負責(zé)加載\jre\lib\ext目錄下的jar包和class文件.
  • 或者由java.ext.dirs系統(tǒng)變量指定路徑中的所有類庫(如javax.開頭的類)缕探,開發(fā)者可以直接使用擴展類加載器魂莫。
    如圖:
    擴展類加載器加載路徑

3. 應(yīng)用程序類加載器(Application ClassLoader)

應(yīng)用程序類加載器(Application ClassLoader):是由sun.misc.Launcher&ApplicationClassLoaer實現(xiàn)。

  • Application ClassLoader是負責(zé)加載用戶類路徑上所指定的類庫爹耗,開發(fā)者可以直接使用這個類加載器耙考,如果應(yīng)用程序沒有自定義過自己的類加載器,一般情況下就是程序默認的類加載器潭兽。

4. 自定義類加載器(User ClassLoader)

**自定義類加載器(User ClassLoader)
**:一般是繼承ClassLoader倦始,重寫findClass方法。
因為JVM自帶的ClassLoader只會從本地文件系統(tǒng)加載標準的Java class文件山卦,因此編寫自定義類加載器可以做到:

    1. 在執(zhí)行非自信代碼之前鞋邑,自動驗證數(shù)字簽名。
    1. 動態(tài)地創(chuàng)建符合用戶特定需要的定制化構(gòu)建類账蓉。
    1. 從特定的場所取得Java class炫狱,例如數(shù)據(jù)庫和網(wǎng)絡(luò)中。

5. 類加載器體系結(jié)構(gòu)(雙親委派模型)

類加載器體系結(jié)構(gòu)

關(guān)于類加載器的加載過程:

    1. 稱為緩存查找環(huán)節(jié)剔猿。第一步是先檢查類加載器中是否已經(jīng)緩存加載了對應(yīng)的類视译。 其中又分為:
    • ① 若存在自定義類加載器,則先檢查自身緩存中是否存在归敬;如果存在則取到酷含。
    • ② 如果自定義緩存不存在鄙早,委托父類查找,也就是應(yīng)用程序類加載器椅亚。
      應(yīng)用程序類加載器同樣也先檢查緩存中是否存在限番,如果存在則取到。
    • ③ 如果應(yīng)用緩存不存在呀舔,則委托它的父類弥虐,既是擴展類加載器。
      擴展類加載器同樣也會先檢查緩存中是否存在媚赖,如果存在則取到霜瘪。
    • ④ 如果擴展類加載器緩存也不存在,則調(diào)用啟動類加載器查找惧磺。
      啟動類加載器也是先檢查是否已經(jīng)加載颖对,如果加載,則取到磨隘。如果未加載缤底,則進入加載環(huán)節(jié)。
    1. 加載環(huán)節(jié)番捂。第二步个唧,在所有類加載器通過緩存都找不到時,則進入類加載環(huán)節(jié)设预。類加載環(huán)節(jié)可分為:
    • ① 啟動類加載器坑鱼。啟動類加載器在緩存找不到后,會根據(jù)它的路徑范圍jre\lib\rt.jar查找加載對應(yīng)類絮缅。如果成功加載鲁沥,則返回;如果不成功耕魄,則進入②画恰。
    • ② 擴展類加載器。擴展類加載器在收到啟動類加載器未成功的情況下吸奴,會根據(jù)它的路徑訪問jre\lib\ext\*.jar查找加載對應(yīng)類允扇。如果成功加載,則返回则奥;如果不成功考润,則進入③。
    • ③ 應(yīng)用程序類加載器读处。應(yīng)用程序類加載器收到擴展類加載器不成功的情況下糊治,會根據(jù)它的路勁訪問ClassPath查找加載對應(yīng)的類。如果成功加載罚舱,則返回井辜;如果不成功绎谦,則進入④。
    • ④ 自定義類加載器粥脚。如果應(yīng)用程序類加載器在收到應(yīng)用程序類加載器不成功的情況下窃肠,會根據(jù)它自定義的路徑訪問查找加載對應(yīng)的類。如果成功加載刷允,則返回冤留;如果不從,則拋出ClassNotFoundExcepiton異常树灶。

上面的流程又可以稱為是雙親委派模型纤怒。

雙親委派模型
雙親委派模型流程
雙親委派模型加載流程

6. 類的加載方式

類的加載方式有三種:

    1. 命令行啟動應(yīng)用的時候由JVM初始化加載。
      用一張圖即可說明破托。請見下圖:


      main方法JVM配置
    1. 通過Class.forName()方法動態(tài)加載肪跋。
    • ① 我們先看測試示例代碼:
    public class TestClassLoader {
        public static void main(String[] args) throws ClassNotFoundException {
            Class.forName("com.jx.Test1");//直接通過Class.forName()來加載類
        }
    }
    
    • ② 接著跟進去查看Class.for()方法的實現(xiàn)歧蒋。
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller); // 這里會調(diào)用ClassLoader.getClassLoader()方法獲得該類的類加載器對象
    }
    
    • ③ 再跟進forName0()方法土砂,是一個native方法。
        private static native Class<?> forName0(String name, boolean initialize,
                                            ClassLoader loader,
                                            Class<?> caller)
        throws ClassNotFoundException;
    
    • ④ 上面的native forName0()方法會調(diào)用類加載器的ClassLoader.loadclass()方法谜洽。
      Class.forName()方法調(diào)用棧

    通過上面調(diào)用棧會發(fā)現(xiàn)Class.forName()方法本質(zhì)上最后會調(diào)用ClassLoader.loadClass()方法萝映。

    1. 通過ClassLoader.loadClass()方法動態(tài)加載。
      直接上ClassLoader.loadClass()方法代碼阐虚,代碼的注釋已經(jīng)說明了很清楚了序臂。
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    
    protected Class<?> loadClass(String name, boolean resolve) //resolve字段表示是否進行【連接】階段處理
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            // 首先,判斷該類是否已經(jīng)加載過了实束。
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) { //如果父類存在
                        // 如果未加載過奥秆,則委派給父類進行加載。
                        c = parent.loadClass(name, false);
                    } else {
                        // 如果父類不存在咸灿,則交給BootstrapClassLoader來加載构订。 什么時候父類不存在呢?其實就是ExtClassLoader不存在父類的情況避矢。
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                    // 如果父類通過緩存+加載都無法找到悼瘾,并拋出ClassNotFoundException異常時,則捕獲異常但不處理审胸。
                }
    
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.                   
                    long t1 = System.nanoTime();
                    // 如果委托的父類們都無法找到該類亥宿,則本加載器自己親自動手去查找。
                    c = findClass(name);
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    

    代碼中有幾個關(guān)鍵調(diào)用需要注意:

    • Class<?> c = findLoadedClass(name)通過緩存查找判斷是否存在該類砂沛。
      進一步查看該方法實現(xiàn)烫扼,又調(diào)用了native findLoadedClass0方法。
      protected final Class<?> findLoadedClass(String name) {
          if (!checkName(name))
              return null;
          return findLoadedClass0(name);
      }
      
      private native final Class<?> findLoadedClass0(String name);
      
    • ② 當parent != null時碍庵,c = parent.loadClass(name, false);材蛛。如果父類不為空圆到,則委派給父類的loadClass()方法執(zhí)行。
      當 parent == null是卑吭,c = findBootstrapClassOrNull(name);父類如果為空時芽淡,則委派給BootstrapClassLoader來查找。
      這里就是雙親委派模型出現(xiàn)了豆赏。
    • ③ 當在經(jīng)過父類們緩存查找和加載后套才,仍然未找到該類,則本加載器會親自進行查找c = findClass(name);舔庶。這個方法很關(guān)鍵废恋。
      protected Class<?> findClass(String name) throws ClassNotFoundException {
              throw new ClassNotFoundException(name);
          }
      
      通常情況下,我們自定義的用戶類加載器通過繼承ClassLoader抽象類后抚岗,重寫findClass()方法是比較的靠譜的或杠。

    到這里已經(jīng)把雙親委派模型講解了,還順帶講解了自定義類加載器宣蔚。

三向抢、ClassLoader代碼解讀-雙親委派模型

通過上面的《通過ClassLoader.loadClass()方法動態(tài)加載》已經(jīng)將雙親委派模型已經(jīng)詳細講解了。
部分補充請查看:
JVM學(xué)習(xí)(二)續(xù)1-ClassLoader代碼解讀-雙親委派模型

四胚委、自定義類加載器詳解

請參考另外一篇文章中有詳細講解挟鸠。
JVM學(xué)習(xí)(二)續(xù)2-自定義類加載器詳解

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市亩冬,隨后出現(xiàn)的幾起案子艘希,更是在濱河造成了極大的恐慌,老刑警劉巖硅急,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件覆享,死亡現(xiàn)場離奇詭異,居然都是意外死亡营袜,警方通過查閱死者的電腦和手機撒顿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來连茧,“玉大人核蘸,你說我怎么就攤上這事⌒パ保” “怎么了客扎?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長罚斗。 經(jīng)常有香客問我徙鱼,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任袱吆,我火速辦了婚禮厌衙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绞绒。我一直安慰自己婶希,他們只是感情好,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布蓬衡。 她就那樣靜靜地躺著喻杈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狰晚。 梳的紋絲不亂的頭發(fā)上筒饰,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音壁晒,去河邊找鬼瓷们。 笑死,一個胖子當著我的面吹牛秒咐,可吹牛的內(nèi)容都是我干的谬晕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼反镇,長吁一口氣:“原來是場噩夢啊……” “哼固蚤!你這毒婦竟也來了娘汞?” 一聲冷哼從身側(cè)響起歹茶,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎你弦,沒想到半個月后惊豺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡禽作,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年尸昧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旷偿。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡烹俗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出萍程,到底是詐尸還是另有隱情幢妄,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布茫负,位于F島的核電站蕉鸳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜潮尝,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一榕吼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧勉失,春花似錦羹蚣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至告匠,卻和暖如春戈抄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背后专。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工划鸽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人戚哎。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓裸诽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親型凳。 傳聞我的和親對象是個殘疾皇子丈冬,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容