Java虛擬機07——類加載器與雙親委派模型

類加載器

類加載器實現了經過一個類的全限定名來獲取此類的二進制字節流,這個動做是放到Java虛擬機外部實現的,以便讓應用程序本身決定如何去獲取所須要的實現類。java

關於類 的惟一性:只有加載它的類加載器和類自己一同確立其在Java虛擬機中的惟一性,即便這兩個類來源於同一個Class文件,被同一個虛擬機加載,只要它們的類加載器不一樣,那這兩個類就一定不一樣bootstrap

public class ClassLoadTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader classLoader = new 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[] b = new byte[inputStream.available()];
                    inputStream.read(b);
                    return defineClass(name,b,0,b.length);
                } catch (IOException e) {
                    e.printStackTrace();
                    throw new ClassNotFoundException(name);
                }
            }
        };
        Object obj = classLoader.loadClass("chap07.ClassLoadTest").newInstance();
        System.out.println(obj.getClass());
        System.out.println(obj instanceof chap07.ClassLoadTest);
    }
}

複製代碼

運行結果: ide

image.png
從第一行看出,這個對象確實是類chap07.ClassLoaderTest實例化出來的對象,可是從第二句能夠發現,這個對象與類chap07.ClassLoaderTest作所屬類型檢查的時候卻返回了false,這是由於虛擬機中存在了兩個ClassLoader,一個是由系統 應用程序類加載器加載的,另一個是由咱們自定義加載器加載的,雖然都來自同一個Class文件,但依然是兩個獨立的類。

雙親委派模型

從Java虛擬機的角度講,只存在兩種不一樣的類加載器:一種是啓動類加載器(Bootstrap ClassLoader),這個類加載器使用C++語言實現,是虛擬機自身的一部分。另外一種就是其餘的類加載器,這些類加載器由Java語言實現,獨立於虛擬機外部,而且所有繼承自抽象類java.lang.ClassLoader。this

從Java開發人員角度看,絕大部分Java程序都會使用到如下3中系統提供的類加載器。spa

  • 啓動類加載器(Bootstrap ClassLoader):啓動類加載器(Bootstrap ClassLoader):前面已經介紹過,這個類將器負責將存放在$JAVA_HOME/lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,而且是虛擬機識別的(僅按照文件名識別,如rt.jar,名字不符合的類庫即便放在lib目錄中也不會被加載)類庫加載到虛擬機內存中。啓動類加載器沒法被Java程序直接引用,用戶在編寫自定義類加載器時,若是須要把加載請求委派給引導類加載器,那直接使用null代替便可
  • 擴展類加載器(Extension ClassLoader):這個加載器由sun.misc.LauncherExtClassLoader實現,它負責加載JAVA_HOME/lib/ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的全部類庫,開發者能夠直接使用擴展類加載器。
  • 應用程序類加載器(Application ClassLoader):這個類加載器由sun.misc.Launcher$AppClassLoader實現。因爲這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,因此通常也稱它爲系統類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者能夠直接使用這個類加載器,若是應用程序中沒有自定義過本身的類加載器,通常狀況下這個就是程序中默認的類加載器。

咱們的應用程序都是由這3種類加載器互相配合進行加載的,若是有必要,還能夠加入本身定義的類加載器。這些類加載器之間的關係通常以下圖所示:code

image.png

這種層次關係稱爲雙親委派模型(Parents Delegation Model)。雙親委派模型要求除了頂層的啓動類加載器外,其他的類加載器都應當有本身的父類加載器。這裏類加載器之間的父子關係通常不會以繼承(Inheritance)的關係來實現,而是都使用組合(Composition)關係來複用父加載器的代碼。cdn

雙親委派模型的工做過程是:若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每個層次的類加載器都是如此,所以全部的加載請求最終都應該傳送到頂層的啓動類加載器中,只有當父加載器反饋本身沒法完成這個加載請求(它的搜索範圍中沒有找到所需的類)時,子加載器纔會嘗試本身去加載。對象

使用雙親委派模型來組織類加載器之間的關係,有一個顯而易見的好處就是Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係。 例如類java.lang.Object,它存放在rt.jar之中,不管哪個類加載器要加載這個類,最終都是委派給處於模型最頂端的啓動類加載器進行加載,所以Object類在程序的各類類加載器環境中都是同一個類。相反,若是沒有使用雙親委派模型,由各個類加載器自行去加載的話,若是用戶本身編寫了一個稱爲java.lang.Object的類,並放在程序的ClassPath中,那系統中將會出現多個不一樣的Object類,Java類型體系中最基礎的行爲也就沒法保證,應用程序也將會變得一片混亂。若是讀者有興趣的話,能夠嘗試去編寫一個與rt.jar類庫中已有類重名的Java類,將會發現能夠正常編譯,但永遠沒法被加載運行。blog

雙親委派的代碼都幾種在java.lang.ClassLoader的loadClass()方法之中繼承

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 {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 若是父類加載器拋出ClassNotFoundException
                // 說明父類加載器沒法完成加載請求
            }

            if (c == null) {
                // 調用自己的findClass方法進行加載
                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;
    }
}
複製代碼
  1. 首先,檢查一下指定名稱的類是否已經加載過,若是加載過了,就不須要再加載,直接返回。
  2. 若是此類沒有加載過,那麼,再判斷一下是否有父加載器;若是有父加載器,則由父加載器加載(即調用parent.loadClass(name, false);).或者是調用bootstrap類加載器來加載。
  3. 若是父加載器及bootstrap類加載器都沒有找到指定的類,那麼調用當前類加載器的findClass方法來完成類加載。
相關文章
相關標籤/搜索