天天看點

聊一聊雙親委派模式

作者:索碼理

說起雙親委派模型,不得不說一下類加載器。

類加載器是什麼?

當我們編譯Java類時,JVM會建立與平台和機器無關的位元組碼。位元組碼存儲在.class檔案中。當我們嘗試使用一個類時,類加載器就會把它加載到記憶體中,然後把位元組碼檔案轉成Class對象。通俗的說類加載器就是将.class檔案轉成Class對象的。

類加載器分類

  1. 啟動類加載器(Bootstrap Class Loader):負責加載存放在 <JAVA_HOME>\lib目錄下的類,或者被-Xbootclasspath參數所指定的路徑中存放的類。比如:rt.jar、java.lang.*包下的類。
  2. 擴充類加載器(Extension Class Loader):負責加載<JAVA_HOME>\lib\ext目錄中,或者被java.ext.dirs系統變量所指定的路徑中所有的類庫。
  3. 應用程式類加載器(Application Class Loader):負責加載使用者類路徑上所有的類庫。

雙親委派模型工作流程

當類加載器收到加載類的請求時,它首先會把請求委派給父加載器去完成,每一層都如此,直到把請求委派給最頂層的啟動類加載器,隻有當父加載器無法加載委派過來的類時,子加載器才會加載。

JVM在加載⼀個類時,會調⽤AppClassLoader的loadClass⽅法來加載這個類,不過在這個⽅法中,會先使⽤ExtClassLoader的loadClass⽅法來加載類,同樣ExtClassLoader的loadClass⽅法中會先使⽤BootstrapClassLoader來加載類,如果BootstrapClassLoader加載到了就直接成功,如果 BootstrapClassLoader沒有加載到,那麼ExtClassLoader就會⾃⼰嘗試加載該類,如果沒有加載到,那麼則會由AppClassLoader來加載這個類。

聊一聊雙親委派模式

雙親委派模型

是以,雙親委派指得是,JVM在加載類時,會委派給ExtClassLoader和BootstrapClassLoader進⾏加載,如果沒加載到才由⾃⼰進⾏加載。

這裡說的雙親并不是說類加載器之間是以繼承方式實作的,而是以組合的方式實作的,通過源碼可以證明這點:

java.lang.ClassLoader#loadClass

private final ClassLoader parent;

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;
        }
    }           

同時,通過源碼我們也可以看到類加載器的加載流程是跟我們描述的一樣的:先檢查請求加載的類型是否已經被加載過,若沒有則調用父加載器的loadClass()方法,若父加載器為空則預設使用啟動類加載器作為父加載器。假如父類加載器加載失敗,抛出ClassNotFoundException異常的話,才調用自己的findClass()方法嘗試進行加載。

雙親委派模型好處

  • 保證唯一性,避免重複加載:類的加載随着它的類加載器一起具備了層級關系,通過這種層級關系避免了重複加載,父類加載器加載了該類,子加載器就無需加載了。
  • 避免核心類被篡改:核心類由啟動類加載器加載,即使使用者自定義同名核心類也不會被加載。