簡述java類加載機制ClassLoader及雙親委派模型

類加載器ClassLoader:

用於將Java類加載到Java虛擬機中,其常用的有以下三種,當然除此之外還有用戶自定義類加載器

  1. 根加載器(Bootstrap)
  2. 擴展加載器
  3. 應用程序類加載器

根加載器(BootstrapClassLoader):

C++編寫。JVM的核心加載器,隨Java面世的第一版加載器。我們之所以安裝好java,配置好環境變量,運行起java,就可以直接使用Object obj = new Object(),List list = new ArrayList()之類代碼,是因爲根加載器默認將${JAVA_HOME}/jre/lib/rt.jar加載到虛擬機中。

擴展加載器(ExtensionClassLoader):

Java編寫。由於Java誕生後不斷的擴展優化,單獨使用擴展類加載器加載${JAVA_HOME}/jre/lib/ext目錄下的jar,庫名通常以javax開頭,例如swing等;擴展類庫主要是爲了兼容舊版本,但某些東西又有了新的解決方案,於是提供擴展類庫。

應用程序類加載器(AppClassLoader):

面向程序員的加載器,會加載環境變量中CLASSPATH下的jar,自己生成的類或者第三方的類均有該加載器加載。

程序測試:

List list = new ArrayList();// java包
SwingNode swingNode = new SwingNode();// javax包
Student student = new Student();// 自定義java類

System.out.println(list.getClass().getClassLoader());
System.out.println(swingNode.getClass().getClassLoader());
System.out.println(student.getClass().getClassLoader());

運行結果: 

 可以看到三種java文件對應的類加載器,根加載器爲null,擴展加載器及應用程序加載器都有一個sun.misc.Launcher前綴,實際上看下圖路徑,該Launcher類就是JVM的入口。

 通過代碼體現父類繼承關係:

List list = new ArrayList();
SwingNode swingNode = new SwingNode();
Student student = new Student();

System.out.println(swingNode.getClass().getClassLoader().getParent());
System.out.println(student.getClass().getClassLoader().getParent());
System.out.println(student.getClass().getClassLoader().getParent().getParent());

 運行結果:

雙親委派模型

當某個類加載器需要加載某個.class文件時,它首先把這個任務委託給他的父類加載器,如果父的類加載器沒有加載,子類加載器纔會嘗試去加載。通俗的說就是:不論哪個class文件需要加載,首先給根加載器,若根加載器沒找到,則交給擴展類加載器,如果還沒找到,纔會給應用程序類加載器,再找不到就會報ClassNotFound異常。

 

ClassLoader源碼分析

ClassLoader爲一個抽象類,在虛擬機啓動後進行類加載會首先調用loadClassInternal方法,而該方法指向loadClass方法加載class文件,而雙親委派模型就是在該方法中實現。該方法首先判斷該類是否已經被加載,若沒被加載,則調用parent.loadClass方法加載(即父加載器),parent.loadClass方法中依然會判斷parent是否爲null,若依然有父類加載器,則繼續遞歸向上調用,直至parent==null。當其父加載器拋出ClassNotFoundException時,說明沒找到,則由子加載器繼續加載,符合雙親委派機制。

public abstract class ClassLoader {

    private final ClassLoader parent;

    // 該方法在虛擬機啓動進行類加載時會立刻調用
    private Class<?> loadClassInternal(String name)
        throws ClassNotFoundException
    {
        if (parallelLockMap == null) {
            synchronized (this) {
                 return loadClass(name);
            }
        } else {
            return loadClass(name);
        }
    }

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

好處

保證源代碼不受污染。假設你自己定義了一個java.lang.String類,去運行,若沒有此機制,會污染原本jdk中帶的String類,導致代碼出現不可預知的問題,程序將變得一片混亂。

破壞雙親委派模型

破壞歷史

在Java誕生以來,歷史上有三次大規模破壞:

第一次:雙親委派模型爲JDK1.2推出,但在此之前就已經存在了ClassLoader,因此需要兼容。上述ClassLoader源碼在loadClass方法中實現的雙親委派,因此如果想破壞最直接的辦法就是繼承ClassLoader並重寫loadClass方法,該方法無疑是破壞性的,但因爲JDK1.2以前都是這麼做的,該接口無奈保留支持重寫,但新的代碼絕對不推薦如此使用。

ClassLoader在JDK1.2之後新增了一個findClass方法,目的就是當父類加載器加載失敗時,將子類加載器的邏輯寫在findClass中完成自定義設計。

第二次:

什麼時候需要打破

未完待續。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章