Tip-Java的雙親委派機制

徐家三少發表於2017-04-17

類載入的雙親委派模型

       雙親委派模型要求除了頂層的啟動類載入器外,其他的類載入器都應當有自己的父類載入器。這裡類載入器之間的父子關係一般不會以繼承關係來實現,而是都使用組合關係來複用父載入器的程式碼

工作過程:

       如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,每一個層次的類載入器都是如此,因此所有的載入請求最終都應該傳遞到頂層的啟動類載入器中,只有當父類載入器反饋自己無法完成這個請求(它的搜尋範圍中沒有找到所需的類)時,子載入器才會嘗試自己去載入

好處:

        Java類隨著它的類載入器一起具備了一種帶有優先順序的層次關係。例如類Object,它放在rt.jar中,無論哪一個類載入器要載入這個類,最終都是委派給啟動類載入器進行載入,因此Object類在程式的各種類載入器環境中都是同一個類。
判斷兩個類是否相同是通過classloader.class這種方式進行的,所以哪怕是同一個class檔案如果被兩個classloader載入,那麼他們也是不同的類

類載入器按照層次,從頂層到底層,分為以下三種:

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

        這個類載入器負責將存放在JAVA_HOME/lib下的,或者被-Xbootclasspath引數所指定的路徑中的,並且是虛擬機器識別的類庫載入到虛擬機器記憶體中。啟動類載入器無法被Java程式直接引用。

2.擴充套件類載入器(Extension ClassLoader)

        這個載入器負責載入JAVA_HOME/lib/ext目錄中的,或者被java.ext.dirs系統變數所指定的路徑中的所有類庫,開發者可以直接使用擴充套件類載入器

3.應用程式類載入器(Application ClassLoader)

        這個載入器是ClassLoader中getSystemClassLoader()方法的返回值,所以一般也稱它為系統類載入器。它負責載入使用者類路徑(Classpath)上所指定的類庫,可直接使用這個載入器,如果應用程式沒有自定義自己的類載入器,一般情況下這個就是程式中預設的類載入器。

       關於這部分的問題,可以直接閱讀sun.misc.Launcher的原始碼,或者看末尾連結給出的文章。

實現自己的載入器

       只需要繼承ClassLoader,並覆蓋findClass方法。在呼叫loadClass方法時,會先根據委派模型在父載入器中載入,如果載入失敗,則會呼叫自己的findClass方法來完成載入。

類載入器如何識別

       通過findLoadedClass來識別是否已經載入某個類,這個方法是findLoadedClass0的一個包裝類,而findLoadedClass0是一個native方法

顯式載入類

在程式碼中顯式載入某個類,有三種方法:

1.    this.getClass().getClassLoader().loadClass()
2.    Class.forName()
3.    MyClassLoader.findClass()複製程式碼

在大多數情況下,並不需要自己實現一個類載入器,而是直接採用URLClassLoader來載入外部Class或者jar檔案。JDK中的AppClassloader也是繼承的URLClassLoader:

public class Test{
    public static void main(String[] args) {
        URL url = null;

        try {
            url = new File("/tmp/Test.class").toURI().toURL();

                ClassLoader loader = new URLClassLoader(urls);
                classLoaderList.add(loader);
                loader.loadClass("com.paddx.test.memory.Test");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}複製程式碼

ClassLoader.loadClass()的載入步驟:

1.    呼叫 findLoadedClass(String) 來檢查是否已經載入類。
2.    在父類載入器上呼叫 loadClass 方法。如果父類載入器為 null,則使用虛擬機器的內建類載入器。
3.    呼叫 findClass(String) 方法查詢類。複製程式碼

       ClassLoader是一個抽象類。Java 預設實現是AppClassLoader。 從原始碼可以看出來findClass和loadClass的不同之處:findClass只是loadClass的其中一步,如果父載入器和根載入器都沒有找到這個類,就會呼叫findClass方法。如果繼承了findClass方法,那麼雙親委派機制就不會被破壞。這其實是一個模板方法模式。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            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 thrown if class not found
                    // from the non-null parent class loader
                }

                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;
        }
    }複製程式碼

由於抽象類的預設findClass實現是:

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }複製程式碼

       所以子類必須實現這個方法。

下面這個文章講的挺好:
blog.csdn.net/jiangwei091…

相關文章