1. 程式人生 > 其它 >Java 類載入器

Java 類載入器

類與類載入器

對於任意一個類,必須由載入它的類載入器和這個類本身一起共同確立其在Java虛擬機器中的唯一性,每一個類載入器,都擁有一個獨立的類名稱空間。
即同一個類,如果由兩個不同的類載入器載入,那麼這兩個類便不相等。這裡的相等包括代表類的Class 物件的equals()方法、
isAssignableFrom()方法、isInstance()方法的返回結果,也包括了使用instanceof關鍵字做物件所屬關係判定等各種情況。

雙親委派模型

JDK8及之前的三層類載入器:

  • 啟動類載入器(Bootstrap Class Loader):這個類載入器負責載入存放在<JAVA_HOME>\lib目錄,或者被-Xbootclasspath引數所指定的路徑中存放的,
    而且是Java虛擬機器能夠識別的
    (按照檔名識別,如rt.jar、tools.jar,名字不符合的類庫即使放在lib目錄中也不會被載入)類庫載入到虛擬機器的記憶體中。
    啟動類載入器無法被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 ccl = ClassLoader.getCallerClassLoader();
                if (ccl != null && ccl != cl && !cl.isAncestor(ccl)) {
                    sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
                }
            }
            return cl;
        }
  • 擴充套件類載入器(Extension Class Loader):這個類載入器是在類sun.misc.Launcher$ExtClassLoader中以Java程式碼的形式實現的。它負責載入<JAVA_HOME>\lib\ext目錄中,
    或者被java.ext.dirs系統變數所指定的路徑中所有的類庫。由於擴充套件類載入器是由Java程式碼實現的,開發者可以直接在程式中使用擴充套件類載入器來載入Class檔案。

  • 應用程式類載入器(Application Class Loader):這個類載入器由sun.misc.Launcher$AppClassLoader來實現。由於應用程式類載入器是ClassLoader類中的getSystem-ClassLoader()方法的返回值,
    所以有些場合中也稱它為“系統類載入器”。它負責載入使用者類路徑(ClassPath)上所有的類庫,在程式中可以直接使用,為程式中的預設載入器。


圖1-1 類載入器雙親委派模型

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

使用雙親委派模型來組織類載入器之間的關係,一個顯而易見的好處就是Java中的類隨著它的類載入器一起具備了一種帶有優先順序的層次關係。
例如類java.lang.Object,它存放在rt.jar之中,無論哪一個類載入器要載入這個類,最終都是委派給處於模型最頂端的啟動類載入器進行載入,
因此Object類在程式的各種類載入器環境中都能夠保證是同一個類。

破壞雙親委派模型

雙親委派很好地解決了各個類載入器協作時基礎型別的一致性問題(越基礎的類由越上層的載入器進行載入)。基礎型別之所以被稱為“基礎”,
是因為它們總是作為被使用者程式碼繼承、呼叫的API存在,但程式設計往往沒有絕對不變的完美規則,如果有基礎型別又要呼叫回用戶的程式碼,那該怎麼辦呢?
一個典型的例子便是JNDI服務,JNDI現在已經是Java的標準服務,它的程式碼由啟動類載入器來完成載入(在JDK 1.3時加入到rt.jar的),肯定屬於Java中很基礎的型別了。
JNDI存在的目的就是對資源進行查詢和集中管理,它需要呼叫由其他廠商實現並部署在應用程式的ClassPath下的JNDI服務提供者介面(Service Provider Interface,SPI)的程式碼,
現在問題來了,啟動類載入器是絕不可能認識、載入這些程式碼的。為了解決這個問題,Java設計者引入了執行緒上下文類載入器(Thread Context ClassLoader)。
這個類載入器可以通過java.lang.Thread類的setContext-ClassLoader()方法進行設定。JNDI服務使用這個執行緒上下文類載入器去載入所需的SPI服務程式碼
這是一種父類載入器去請求子類載入器完成類載入的行為,這種行為實際上是打通了雙親委派模型的層次結構來逆向使用類載入器,已經違背了雙親委派模型的一般性原則。