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

目錄


一、類加載器

還記得類加載機制嗎?類加載機制的各階段是加載、連接(驗證、準備、解析)、初始化、使用、卸載??蓞⒖忌掀恼拢?a href="http://www.itdecent.cn/p/4f5b11c11558" target="_blank">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)核中;
    當(dāng)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);
      
    • ② 當(dāng)parent != null時,c = parent.loadClass(name, false);。如果父類不為空,則委派給父類的loadClass()方法執(zhí)行。
      當(dāng) parent == null是,c = findBootstrapClassOrNull(name);父類如果為空時,則委派給BootstrapClassLoader來查找。
      這里就是雙親委派模型出現(xiàn)了。
    • ③ 當(dāng)在經(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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