JVM筆記:Java虛擬機的類加載器和雙親委派機制

  • 類與類加載器

    類加載器雖然只用於實現類的加載動做,可是它在Java程序中起到的做用卻遠遠不限於類加載階段。對於仍和一個類,都須要由加載它的類加載器和這個類自己一同確立其在Java虛擬機中的惟一性,每個類加載器,都擁有一個獨立的類名稱空間。java

    換而言之,判斷兩個類是否相等,只有在這兩個類是由同一個類加載器加載的前提下才有意義,不然,即便兩個類來源於同一個Class文件,被同一個虛擬機加載,只要加載他們的類加載器不一樣,那這兩個類一定不相同。 這裏指的相等,包括表明類的Class對象的equal方法、isAssignableFrom方法、isInstance方法的返回結果,也包括使用instanceof關鍵字作對象所屬關係判斷等狀況。以下面這個例子。bootstrap

public class MyClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        try {
            String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
            InputStream inputStream = 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) {
            throw new ClassNotFoundException(name);
        }
    }
}

public class MyClass {
    public static void main(String[] args) throws Exception {
        Object object = new MyClassLoader().loadClass("com.verzqli.lib.MyClass").newInstance();
        System.out.println("objectClass = [" + object.getClass() + "]");
        System.out.println("objectInstanceOf= [" + (object instanceof com.verzqli.lib.MyClass) + "]");
    }
}
輸出結果:
objectClass = [class com.verzqli.lib.MyClass]
objectInstanceOf= [false]
複製代碼

上例中自定義了一個類加載器MyClassLoader,用它去加載了MyClass類,第一個輸出結果代表他實例化的確實是MyClass,可是在後續的類型檢查中卻返回了false,由於虛擬機中存在了兩個MyClass類,一個由系統應用程序類加載器加載的,另外一個是由咱們自定義的類加載器加載的,雖然都是來自於同一個Class文件,但依然是兩個獨立的類,因此類型檢查是結果天然爲false。bash

  • 雙親委派機制

    從虛擬機的角度來看,只存在兩種不一樣的類加載器:ide

    • 啓動類加載器(Bootstrap ClassLoader):這個類加載器使用C++實現(只限於HotSpot),是虛擬機自身的一部分。
    • 全部其餘的類加載器:這些類加載器都由Java實現,獨立於虛擬機外部,而且所有都繼承自抽象類java.lang.classLoader

    但從開發人員角度來看,類加載器還能夠劃分的更細緻一些,主要都會使用到一下三種系統提供的類加載器:ui

    • 啓動類加載器(Bootstrap ClassLoader):負責加載<JAVA_HOME>\lib(相似a.jar),或者被-X bootclasspath參數指定路徑中且是能被虛擬機識別的類庫假如到虛擬機內存中。改加載器不能被Java程序直接引用,在編寫自定義類加載器時,若是須要把加載請求委派給啓動類加載器,那直接使用null替代便可。
/**
     * Returns the class loader for the class.  Some implementations may use
     * null to represent the bootstrap class loader. This method will return
     * null in such implementations if this class was loaded by the bootstrap
     * class loader.
     */
 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;
    }
複製代碼
  • 擴展類加載器(Extension ClassLoader):這個加載器由sun.misc.Launch$ExtClassLoader實現,他負責加載<JAVA_HOME>\lib\ext目錄中,或者被java.ext.dirs系統變量鎖指定的路徑中的全部類庫,開發者能夠直接使用它。this

  • 應用程序加載器(ApplicationClassLoader):這個加載器由sun.misc.Launch$AppClassLoader實現,,這個類加載器同事也是getSystemClassLoader()方法的返回值,因此也稱系統類加載器。它負責加載用戶類路徑上所指定的類庫,開發者能夠直接使用這個加載器,通常狀況下若是應用程序沒有自定義本身的類加載器,那麼該加載器就是程序中默認的加載器。spa

通常狀況下咱們的應用程序都是由着三種類加載器互相配合進行加載的,若是有必要,能夠加入本身定義的類加載器。他們的關係以下圖。 code

雙親委派過程.png

上圖中展現的類加載器之間的這種層次關係,稱爲類加載器的雙親委派模型(Parent Delegation Model):除了最頂層的啓動類加載器以外,其他的類加載器都應當有本身的父類加載器(這裏類加載器之間的斧子關係通常用組合關係來實現,並非繼承關係)。cdn

雙親委派的工做機制:若是一個類加載器收到了加載類的請求,它自身不會先去加載這個類,而是把這個請求委派給父類加載器去加載,若是父類加載器還有父類,那麼繼續向上請求委派,最後全部的加載請求都到了頂層的啓動類加載器中。只有當父類加載器返回本身沒法加載這個類時(在它的加載範圍內沒找到這個類)時,子類加載器纔會嘗試本身加載。對象

雙親委派模型有一個顯而易見的好處就是Java類隨着他的類加載器一塊兒具有了一種帶有優先級的層次關係,例如類java.lang.Object,他存放於rt.jar之中,不管哪個類加載器要加載這個類,最終都要委派給啓動類加載器來進行加載,因此Object類在程序的各類類加載器環境中都是一個類。相反若是沒有雙親委派模型,由各個類加載器自行去加載的話,那麼系統中將會出現多個不一樣的Object類,這樣連Java類型體系中最基礎的行爲都沒法獲得保障,應用程序也會一片混亂。

假若你編寫了一個包名和類名都和父類加載器中加載範圍內存在的類如出一轍,那麼最後加載出來的類只會是父類加載器中那個類,你自定義的這個類不會被加載出來。

雙親委派實現的功能很重要,可是其實現卻很是簡單,以下面加載類的代碼:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先檢查該類是否已經被加載過了
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 若是父類加載器不爲空,就調用父類加載器的加載方法
                    if (parent != null) {  
                        c = parent.loadClass(name, false);
                    } else {
                       // 若是父類爲null,那麼當前必定是啓動類加載器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 若是父類加載器拋出ClassNotFoundException 異常,說明父類加載器完成加載
                }

                if (c == null) {
                    //當父類加載器沒法加載到類的時候,才調用自身的加載方法去加載類
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // 定義類裝入器;記錄統計信息
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
複製代碼
相關文章
相關標籤/搜索