Java類載入原理解析

不放棄的泉發表於2017-10-06

1       基本資訊

摘要:

每個java開發人員對java.lang.ClassNotFoundExcetpion這個異常肯定都不陌生,這背後就涉及到了java技術體系中的類載入。Java的類載入機制是java技術體系中比較核心的部分,雖然和大部分開發人員直接打交道不多,但是對其背後的機理有一定理解有助於排查程式中出現的類載入失敗等技術問題,對理解java虛擬機器的連線模型和java語言的動態性都有很大幫助。

由於關於java類載入的內容較多,所以打算分三篇文章簡述一下:

第一篇:java類載入原理解析

第二篇:外掛環境下類載入原理解析

第三篇:執行緒上下文類載入器

2       Java虛擬機器類載入器結構簡述

2.1    JVM三種預定義型別類載入器

我們首先看一下JVM預定義的三種型別類載入器,當一個 JVM 啟動的時候,Java 預設開始使用如下三種型別類裝入器:

啟動(Bootstrap)類載入器:引導類裝入器是用原生程式碼實現的類裝入器,它負責將 <Java_Runtime_Home>/lib 下面的類庫載入到記憶體中。由於引導類載入器涉及到虛擬機器本地實現細節,開發者無法直接獲取到啟動類載入器的引用,所以不允許直接通過引用進行操作。可以通過System.out.println(System.getProperty("sun.boot.class.path"));得到啟動類載入器可以載入的路徑,一般將java class檔案(不是jar包)放在<Java_Runtime_Home>/classes路徑中將會被啟動類載入器載入。

標準擴充套件(Extension)類載入器:擴充套件類載入器是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 實現的。它負責將 < Java_Runtime_Home >/lib/ext 或者由系統變數 java.ext.dir 指定位置中的類庫載入到記憶體中。開發者可以直接使用標準擴充套件類載入器。可以通過System.out.println(System.getProperty("java.ext.dirs"));得到擴充套件類載入器可以載入的路徑,將jar檔案放在這個路徑中將會被擴充套件類載入器載入。

系統(System)類載入器:系統類載入器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實現的。它負責將系統類路徑(CLASSPATH)中指定的類庫載入到記憶體中。開發者可以直接使用系統類載入器。由於ClassLoader.getSystemClassLoader()返回的是這個類載入器,因此得名系統類載入器。

除了以上列舉的三種類載入器,還有一種比較特殊的型別就是執行緒上下文類載入器,這個將在後面單獨介紹。

2.2    類載入雙親委派機制介紹和分析

       在這裡,需要著重說明的是,JVM在載入類時預設採用的是雙親委派機制。通俗的講,就是某個特定的類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,依次遞迴,如果父類載入器可以完成類載入任務,就成功返回;只有父類載入器無法完成此載入任務時,才自己去載入。關於虛擬機器預設的雙親委派機制,我們可以從系統類載入器和標準擴充套件類載入器為例作簡單分析。

                 
                 圖一 標準擴充套件類載入器繼承層次圖

                   
        圖二 系統類載入器繼承層次圖

       通過圖一和圖二我們可以看出,類載入器均是繼承自java.lang.ClassLoader抽象類。我們下面我們就看簡要介紹一下java.lang.ClassLoader中幾個最重要的方法:

//載入指定名稱(包括包名)的二進位制型別,供使用者呼叫的介面

public Class<?> loadClass(String name)throws ClassNotFoundException{//…}

//載入指定名稱(包括包名)的二進位制型別,同時指定是否解析(但是,這裡的resolve引數不一定真正能達到解析的效果~_~),供繼承用

protectedsynchronized Class<?>loadClass(String name, boolean resolve)throws ClassNotFoundException{//…}

//findClass方法一般被loadClass方法呼叫去載入指定名稱類,供繼承用

protected Class<?> findClass(String name) throws ClassNotFoundException {//…}

//定義型別,一般在findClass方法中讀取到對應位元組碼後呼叫,可以看出不可繼承(說明:JVM已經實現了對應的具體功能,解析對應的位元組碼,產生對應的內部資料結構放置到方法區,所以無需覆寫,直接呼叫就可以了)

protected final Class<?> defineClass(String name, byte[] b, int off, int len)

throws ClassFormatError{//…}

       通過進一步分析標準擴充套件類載入器(sun.misc.Launcher$ExtClassLoader)和系統類載入器(sun.misc.Launcher$AppClassLoader)的程式碼以及其公共父類(java.net.URLClassLoader和java.security.SecureClassLoader)的程式碼可以看出,都沒有覆寫java.lang.ClassLoader中預設的載入委派規則---loadClass(…)方法。既然這樣,我們就可以通過分析java.lang.ClassLoader中的loadClass(String name)方法的程式碼就可以分析出虛擬機器預設採用的雙親委派機制到底是什麼模樣:

public Class<?>loadClass(String name)throws ClassNotFoundException {

        return loadClass(name,false);

}

protectedsynchronized Class<?> loadClass(String name,boolean resolve)

            throws ClassNotFoundException {

        //首先判斷該型別是否已經被載入

        Class c = findLoadedClass(name);

        if (c ==null) {

            //如果沒有被載入,就委託給父類載入或者委派給啟動類載入器載入

            try {

                if (parent !=null) {

//如果存在父類載入器,就委派給父類載入器載入

                    c = parent.loadClass(name,false);

                } else {

//如果不存在父類載入器,就檢查是否是由啟動類載入器載入的類,通過呼叫本地方法native Class findBootstrapClass(String name)

                    c = findBootstrapClass0(name);

                }

            } catch (ClassNotFoundException e) {

        //如果父類載入器和啟動類載入器都不能完成載入任務,才呼叫自身的載入功能

                c = findClass(name);

            }

        }

        if (resolve) {

            resolveClass(c);

        }

        return c;

    }

    通過上面的程式碼分析,我們可以對JVM採用的雙親委派類載入機制有了更感性的認識,下面我們就接著分析一下啟動類載入器、標準擴充套件類載入器和系統類載入器三者之間的關係。可能大家已經從各種資料上面看到了如下類似的一幅圖片:


                    
                             圖三 類載入器預設委派關係圖

上面圖片給人的直觀印象是系統類載入器的父類載入器是標準擴充套件類載入器,標準擴充套件類載入器的父類載入器是啟動類載入器,下面我們就用程式碼具體測試一下:

示例程式碼:

public static void main(String[] args) {
   
try {
     System.out.println(ClassLoader.getSystemClassLoader());
     System.out.println(ClassLoader.getSystemClassLoader().getParent();
     System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
   } 
catch (Exception e) {
       e.printStackTrace();
   }
}


說明:通過java.lang.ClassLoader.getSystemClassLoader()可以直接獲取到系統類載入器。

程式碼輸出如下:

sun.misc.Launcher$AppClassLoader@197d257

sun.misc.Launcher$ExtClassLoader@7259da

null

    通過以上的程式碼輸出,我們可以判定系統類載入器的父載入器是標準擴充套件類載入器,但是我們試圖獲取標準擴充套件類載入器的父類載入器時確得到了null,就是說標準擴充套件類載入器本身強制設定父類載入器為null。我們還是藉助於程式碼分析一下:

     我們首先看一下java.lang.ClassLoader抽象類中預設實現的兩個建構函式:

    protected ClassLoader() {

        SecurityManager security = System.getSecurityManager();

        if (security !=null) {

            security.checkCreateClassLoader();

        }

        //預設將父類載入器設定為系統類載入器,getSystemClassLoader()獲取系統類載入器

        this.parent = getSystemClassLoader();

        initialized = true;

    }

    protected ClassLoader(ClassLoader parent) {

        SecurityManager security = System.getSecurityManager();

        if (security !=null) {

            security.checkCreateClassLoader();

        }

        //強制設定父類載入器

        this.parent = parent;

        initialized = true;

    }

    我們再看一下ClassLoader抽象類中parent成員的宣告:

       // The parent class loader for delegation

private ClassLoaderparent;

宣告為私有變數的同時並沒有對外提供可供派生類訪問的public或者protected設定器介面(對應的setter方法),結合前面的測試程式碼的輸出,我們可以推斷出:

1.              系統類載入器(AppClassLoader)呼叫ClassLoader(ClassLoader parent)建構函式將父類載入器設定為標準擴充套件類載入器(ExtClassLoader)。(因為如果不強制設定,預設會通過呼叫getSystemClassLoader()方法獲取並設定成系統類載入器,這顯然和測試輸出結果不符。)

2.               擴充套件類載入器(ExtClassLoader呼叫ClassLoader(ClassLoader parent)建構函式將父類載入器設定為null。(因為如果不強制設定,預設會通過呼叫getSystemClassLoader()方法獲取並設定成系統類載入器,這顯然和測試輸出結果不符。)

     現在我們可能會有這樣的疑問:擴充套件類載入器(ExtClassLoader)的父類載入器被強制設定為null了,那麼擴充套件類載入器為什麼還能將載入任務委派給啟動類載入器呢?

              

         圖四 標準擴充套件類載入器和系統類載入器成員大綱檢視

                  
           
           圖五 擴充套件類載入器和系統類載入器公共父類成員大綱檢視

    通過圖四和圖五可以看出,標準擴充套件類載入器和系統類載入器及其父類(java.net.URLClassLoader和java.security.SecureClassLoader都沒有覆寫java.lang.ClassLoader中預設的載入委派規則---loadClass(…)方法。有關java.lang.ClassLoader中預設的載入委派規則前面已經分析過,如果父載入器為null,則會呼叫本地方法進行啟動類載入嘗試。所以,圖三中,啟動類載入器、標準擴充套件類載入器和系統類載入器之間的委派關係事實上是仍就成立的。(在後面的使用者自定義類載入器部分,還會做更深入的分析)。

2.3    類載入雙親委派示例

以上已經簡要介紹了虛擬機器預設使用的啟動類載入器、標準擴充套件類載入器和系統類載入器,並以三者為例結合JDK程式碼對JVM預設使用的雙親委派類載入機制做了分析。下面我們就來看一個綜合的例子。首先在eclipse中建立一個簡單的java應用工程,然後寫一個簡單的JavaBean如下:

package classloader.test.bean;

    publicclass TestBean {

        public TestBean() {}

}

在現有當前工程中另外建立一測試類(ClassLoaderTest.java)內容如下:

測試一:

publicclass ClassLoaderTest {

    publicstaticvoid main(String[] args) {

        try {

            //檢視當前系統類路徑中包含的路徑條目

            System.out.println(System.getProperty("java.class.path"));

//呼叫載入當前類的類載入器(這裡即為系統類載入器)載入TestBean

Class typeLoaded = Class.forName("classloader.test.bean.TestBean");

//檢視被載入的TestBean型別是被那個類載入器載入的

            System.out.println(typeLoaded.getClassLoader());

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}

對應的輸出如下:

D:"DEMO"dev"Study"ClassLoaderTest"bin

sun.misc.Launcher$AppClassLoader@197d257

(說明:當前類路徑預設的含有的一個條目就是工程的輸出目錄)

測試二:

將當前工程輸出目錄下的…/classloader/test/bean/TestBean.class打包進test.jar剪貼< Java_Runtime_Home >/lib/ext目錄下(現在工程輸出目錄下和JRE擴充套件目錄下都有待載入型別的class檔案)。再執行測試一測試程式碼,結果如下:

D:"DEMO"dev"Study"ClassLoaderTest"bin

sun.misc.Launcher$ExtClassLoader@7259da

對比測試一和測試二,我們明顯可以驗證前面說的雙親委派機制,系統類載入器在接到載入classloader.test.bean.TestBean型別的請求時,首先將請求委派給父類載入器(標準擴充套件類載入器),標準擴充套件類載入器搶先完成了載入請求。

    測試三:

test.jar拷貝一份到< Java_Runtime_Home >/lib下,執行測試程式碼,輸出如下:

D:"DEMO"dev"Study"ClassLoaderTest"bin

sun.misc.Launcher$ExtClassLoader@7259da

   測試三和測試二輸出結果一致。那就是說,放置到< Java_Runtime_Home >/lib目錄下的TestBean對應的class位元組碼並沒有被載入,這其實和前面講的雙親委派機制並不矛盾。虛擬機器出於安全等因素考慮,不會載入< Java_Runtime_Home >/lib存在的陌生類開發者通過將要載入的非JDK自身的類放置到此目錄下期待啟動類載入器載入是不可能的。但是將class檔案放到啟動類載入器載入路徑下卻可以被載入。做個進一步驗證,刪除< Java_Runtime_Home >/lib/ext目錄下和工程輸出目錄下的TestBean對應的class檔案,然後再執行測試程式碼,則將會有ClassNotFoundException異常丟擲。有關這個問題,大家可以在java.lang.ClassLoader中的loadClass(String name, boolean resolve)方法中設定相應斷點執行測試三進行除錯,會發現findBootstrapClass0()會丟擲異常,然後在下面的findClass方法中被載入,當前執行的類載入器正是擴充套件類載入器(sun.misc.Launcher$ExtClassLoader),這一點可以通過JDT中變數檢視檢視驗證。

3       java程式動態擴充套件方式

Java的連線模型允許使用者執行時擴充套件引用程式,既可以通過當前虛擬機器中預定義的載入器載入編譯時已知的類或者介面,又允許使用者自行定義類裝載器,在執行時動態擴充套件使用者的程式。通過使用者自定義的類裝載器,你的程式可以裝載在編譯時並不知道或者尚未存在的類或者介面,並動態連線它們並進行有選擇的解析。

       執行時動態擴充套件java應用程式有如下兩個途徑:

3.1    呼叫java.lang.Class.forName(…)

這個方法其實在前面已經討論過,在後面的問題2解答中說明了該方法呼叫會觸發那個類載入器開始載入任務。這裡需要說明的是多引數版本的forName(…)方法:

public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException

這裡的initialize引數是很重要的,可以決定被載入同時是否完成初始化的工作(說明: 單引數版本的forName方法預設是完成初始化的,檢視原始碼可以發現呼叫的是forName0(className, true, ClassLoader.getCallerClassLoader());傳的引數是true).有些場景下,可以呼叫forName(String className, boolean initialize, ClassLoaderloader)這個版本將initialize設定為false不進行初始化。

3.2    使用者自定義類載入器

通過前面的分析,我們可以看出,除了和本地實現密切相關的啟動類載入器之外,包括標準擴充套件類載入器和系統類載入器在內的所有其他類載入器我們都可以當做自定義類載入器來對待,唯一區別是是否被虛擬機器預設使用。前面的內容中已經對java.lang.ClassLoader抽象類中的幾個重要的方法做了介紹,這裡就簡要敘述一下一般使用者自定義類載入器的工作流程吧(可以結合後面問題解答一起看):

1、首先檢查請求的型別是否已經被這個類裝載器裝載到名稱空間中了,如果已經裝載,直接返回;否則轉入步驟2

2、委派類載入請求給父類載入器(更準確的說應該是雙親類載入器,真個虛擬機器中各種類載入器最終會呈現樹狀結構),如果父類載入器能夠完成,則返回父類載入器載入的Class例項;否則轉入步驟3

3、呼叫本類載入器的findClass(…)方法,試圖獲取對應的位元組碼,如果獲取的到,則呼叫defineClass(…)匯入型別到方法區;如果獲取不到對應的位元組碼或者其他原因失敗,返回異常給loadClass(…), loadClass(…)轉拋異常,終止載入過程(注意:這裡的異常種類不止一種)。

       (說明:這裡說的自定義類載入器是指JDK 1.2以後版本的寫法,即不覆寫改變java.lang.loadClass(…)已有委派邏輯情況下)

4       常見問題分析:

4.1    由不同的類載入器載入的指定型別還是相同的型別嗎?

在Java中,一個類用其完全匹配類名(fully qualified class name)作為標識,這裡指的完全匹配類名包括包名和類名。但在JVM中一個類用其全名和一個載入類ClassLoader的例項作為唯一標識,不同類載入器載入的類將被置於不同的名稱空間.我們可以用兩個自定義類載入器去載入某自定義型別(注意,不要將自定義型別的位元組碼放置到系統路徑或者擴充套件路徑中,否則會被系統類載入器或擴充套件類載入器搶先載入),然後用獲取到的兩個Class例項進行java.lang.Object.equals(…)判斷,將會得到不相等的結果。這個大家可以寫兩個自定義的類載入器去載入相同的自定義型別,然後做個判斷;同時,可以測試載入java.*型別,然後再對比測試一下測試結果。

4.2    在程式碼中直接呼叫Class.forName(String name)方法,到底會觸發那個類載入器進行類載入行為?

Class.forName(String name)預設會使用載入呼叫類的類載入器來進行類載入。我們直接來分析一下對應的jdk的程式碼:

//java.lang.Class.java

       publicstatic Class<?>forName(String className)throws ClassNotFoundException {

return forName0(className,true,ClassLoader.getCallerClassLoader());

}

//java.lang.ClassLoader.java

// Returns the invoker's class loader, or null if none.

static ClassLoadergetCallerClassLoader() {

              // 獲取呼叫類(caller)的型別

        Class caller = Reflection.getCallerClass(3);

              // This can be null if the VM is requesting it

        if (caller ==null) {

            returnnull;

        }

        //呼叫java.lang.Class中本地方法獲取載入該呼叫類(caller)的ClassLoader

        return caller.getClassLoader0();

}

//java.lang.Class.java

//虛擬機器本地實現,獲取當前類的類載入器,前面介紹的ClassgetClassLoader()也使用此方法

native ClassLoader getClassLoader0();

4.3    在編寫自定義類載入器時,如果沒有設定父載入器,那麼父載入器是?

前面講過,在不指定父類載入器的情況下,預設採用系統類載入器。可能有人覺得不明白,現在我們來看一下JDK對應的程式碼實現。眾所周知,我們編寫自定義的類載入器直接或者間接繼承自java.lang.ClassLoader抽象類,對應的無參預設建構函式實現如下:

//摘自java.lang.ClassLoader.java

protected ClassLoader() {

           SecurityManager security = System.getSecurityManager();

           if (security !=null) {

               security.checkCreateClassLoader();

           }

           this.parent = getSystemClassLoader();

           initialized = true;

}

我們再來看一下對應的getSystemClassLoader()方法的實現:

privatestaticsynchronizedvoid initSystemClassLoader() {

           //...

           sun.misc.Launcher l = sun.misc.Launcher.getLauncher();

           scl = l.getClassLoader();

           //...

}

我們可以寫簡單的測試程式碼來測試一下:

System.out.println(sun.misc.Launcher.getLauncher().getClassLoader());

本機對應輸出如下:

sun.misc.Launcher$AppClassLoader@197d257

所以,我們現在可以相信當自定義類載入器沒有指定父類載入器的情況下,預設的父類載入器即為系統類載入器。同時,我們可以得出如下結論:

即時使用者自定義類載入器不指定父類載入器,那麼,同樣可以載入如下三個地方的類:

1.    <Java_Runtime_Home>/lib下的類

2.    < Java_Runtime_Home >/lib/ext下或者由系統變數java.ext.dir指定位置中的類

3.    當前工程類路徑下或者由系統變數java.class.path指定位置中的類

4.4    在編寫自定義類載入器時,如果將父類載入器強制設定為null,那麼會有什麼影響?如果自定義的類載入器不能載入指定類,就肯定會載入失敗嗎?

JVM規範中規定如果使用者自定義的類載入器將父類載入器強制設定為null,那麼會自動將啟動類載入器設定為當前使用者自定義類載入器的父類載入器(這個問題前面已經分析過了)。同時,我們可以得出如下結論:

即時使用者自定義類載入器不指定父類載入器,那麼,同樣可以載入到<Java_Runtime_Home>/lib下的類,但此時就不能夠載入<Java_Runtime_Home>/lib/ext目錄下的類了。

    說明:問題3和問題4的推斷結論是基於使用者自定義的類載入器本身延續了java.lang.ClassLoader.loadClass)預設委派邏輯,如果使用者對這一預設委派邏輯進行了改變,以上推斷結論就不一定成立了,詳見問題5

4.5    編寫自定義類載入器時,一般有哪些注意點?

1.      一般儘量不要覆寫已有的loadClass(…)方法中的委派邏輯

一般在JDK 1.2之前的版本才這樣做,而且事實證明,這樣做極有可能引起系統預設的類載入器不能正常工作。在JVM規範和JDK文件中(1.2或者以後版本中),都沒有建議使用者覆寫loadClass(…)方法,相比而言,明確提示開發者在開發自定義的類載入器時覆寫findClass(…)邏輯。舉一個例子來驗證該問題:

//使用者自定義類載入器WrongClassLoader.Java(覆寫loadClass邏輯)

publicclassWrongClassLoaderextends ClassLoader {

        publicClass<?>loadClass(String name)throws ClassNotFoundException {

            returnthis.findClass(name);

        }

        protected Class<?>findClass(String name)throws ClassNotFoundException {

            //假設此處只是到工程以外的特定目錄D:/library下去載入類

            具體實現程式碼省略

        }

}

    通過前面的分析我們已經知道,使用者自定義類載入器(WrongClassLoader)的默

       認的類載入器是系統類載入器,但是現在問題4種的結論就不成立了。大家可以簡

       單測試一下,現在<Java_Runtime_Home>/lib、< Java_Runtime_Home >/lib/ext和工

       程類路徑上的類都載入不上了。

       //問題5測試程式碼一

publicclass WrongClassLoaderTest {

        publicstaticvoid main(String[] args) {

           try {

               WrongClassLoader loader =new WrongClassLoader();

               Class classLoaded = loader.loadClass("beans.Account");

               System.out.println(classLoaded.getName());

               System.out.println(classLoaded.getClassLoader());

           } catch (Exception e) {

               e.printStackTrace();

           }

        }

}

(說明:D:"classes"beans"Account.class物理存在的)

輸出結果:

java.io.FileNotFoundException: D:"classes"java"lang"Object.class (系統找不到指定的路徑。)

    at java.io.FileInputStream.open(Native Method)

    at java.io.FileInputStream.<init>(FileInputStream.java:106)

    at WrongClassLoader.findClass(WrongClassLoader.java:40)

    at WrongClassLoader.loadClass(WrongClassLoader.java:29)

    at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)

    at java.lang.ClassLoader.defineClass1(Native Method)

    at java.lang.ClassLoader.defineClass(ClassLoader.java:620)

    at java.lang.ClassLoader.defineClass(ClassLoader.java:400)

    at WrongClassLoader.findClass(WrongClassLoader.java:43)

    at WrongClassLoader.loadClass(WrongClassLoader.java:29)

    at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)

Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object

    at java.lang.ClassLoader.defineClass1(Native Method)

    at java.lang.ClassLoader.defineClass(ClassLoader.java:620)

    at java.lang.ClassLoader.defineClass(ClassLoader.java:400)

    at WrongClassLoader.findClass(WrongClassLoader.java:43)

    at WrongClassLoader.loadClass(WrongClassLoader.java:29)

    at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)

這說明,連要載入的型別的超型別java.lang.Object都載入不到了。這裡列舉的由於覆寫loadClass)引起的邏輯錯誤明顯是比較簡單的,實際引起的邏輯錯誤可能複雜的多。

//問題5測試二

//使用者自定義類載入器WrongClassLoader.Java(不覆寫loadClass邏輯)

publicclassWrongClassLoaderextends ClassLoader {

        protected Class<?>findClass(String name)throws ClassNotFoundException {

            //假設此處只是到工程以外的特定目錄D:/library下去載入類

            具體實現程式碼省略

        }

}

將自定義類載入器程式碼WrongClassLoader.Java做以上修改後,再執行測試程式碼,輸出結果如下:

beans.Account

WrongClassLoader@1c78e57

這說明,beans.Account載入成功,且是由自定義類載入器WrongClassLoader載入。

這其中的原因分析,我想這裡就不必解釋了,大家應該可以分析的出來了。

2.      2、正確設定父類載入器

通過上面問題4和問題5的分析我們應該已經理解,個人覺得這是自定義使用者類載入器時最重要的一點,但常常被忽略或者輕易帶過。有了前面JDK程式碼的分析作為基礎,我想現在大家都可以隨便舉出例子了。

3.      3、保證findClass(String )方法的邏輯正確性

事先儘量準確理解待定義的類載入器要完成的載入任務,確保最大程度上能夠獲取到對應的位元組碼內容。

4.6    如何在執行時判斷系統類載入器能載入哪些路徑下的類?

一是可以直接呼叫ClassLoader.getSystemClassLoader()或者其他方式獲取到系統類載入器(系統類載入器和擴充套件類載入器本身都派生自URLClassLoader),呼叫URLClassLoader中的getURLs()方法可以獲取到;

二是可以直接通過獲取系統屬性java.class.path 來檢視當前類路徑上的條目資訊 , System.getProperty("java.class.path")

4.7    如何在執行時判斷標準擴充套件類載入器能載入哪些路徑下的類?

方法之一:

try {
               URL[] extURLs = ((URLClassLoader)ClassLoader.getSystemClassLoader().getParent()).getURLs();

              for (int i = 0; i < extURLs.length; i++) {

                     System.out.println(extURLs[i]);

              }

       } catch (Exception e) {//…}

       本機對應輸出如下:

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/dnsns.jar

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/localedata.jar

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunjce_provider.jar

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunpkcs11.jar

5       總結:

寫這篇文章的初衷是通過分析JDK相關程式碼來驗證一些載入規則,核心就是藉助雙親委派機制來分析一個載入請求處理的主要過程,所列舉的幾個簡單的例子實際意義不大,因為遇到的情況往往比例子情況複雜的多。

下一篇文章,我會重點分析一下Eclipse的外掛類載入器,並分析一下外掛環境下的類載入和普通java應用場景中的類載入有什麼不同,並會提供一個比較完整的類載入器。

外掛類載入器就是一個由eclipse開發的一個使用者自定義類載入器,所以分析時候用到的一些基本的東西都是在這篇文章中涉及到的。

    這篇文章寫的時候時間比較緊,亂糟糟的,大家見諒。中間參考了JVM規範、jdk文件和程式碼、《Inside Java Virtual Machine》一書等資料。

    文章中肯定包含了一些錯誤,歡迎指出,謝謝!

轉載 http://www.blogjava.net/zhuxing/archive/2008/08/08/220841.html

相關文章