java類加載器原理

這篇文章試圖解決下面一些問題:
類加載原理
引導類加載器,擴展類加載器和系統類加載器
如何知道某個類是哪一個類加載器加載的
如何獲得系統類加載器加載了那些類

首先咱們要分析類加載原理,java中默認有三種類加載器:引導類加載器,擴展類加載器,系統類加載器(也叫應用類加載器) java

 

引導類加載器負責加載jdk中的系統類,這種類加載器都是用c語言實現的,在java程序中沒有辦法得到這個類加載器,對於java程序是一個概念而已,基本上不用考慮它的存在,像String,Integer這樣的類都是由引導類加載器加載器的.
擴展類加載器負責加載標準擴展類,通常使用java實現,這是一個真正的java類加載器,負責加載jre/lib/ext中的類,和普通的類加載器同樣,其實這個類加載器對咱們來講也不是很重要,咱們能夠經過java程序得到這個類加載器。
系統類加載器,加載第一個應用類的加載器(其實這個定義並不許確,下面你將會看到),也就是執行java MainClass 時加載MainClass的加載器,這個加載器使用java實現,使用的很普遍,負責加載classpath中指定的類。

類加載器之間有必定的關係(父子關係),咱們能夠認爲擴展類加載器的父加載器是引導類加載器(固然不這樣認爲也是能夠的,由於引導類加載器表如今java中就是一個null),不過系統類加載器的父加載器必定是擴展類加載器,類加載器在加載類的時候會先給父加載器一個機會,只有父加載器沒法加載時纔會本身去加載。

咱們沒法得到引導類加載器,由於它是使用c實現的,並且使用引導類加載器加載的類經過getClassLoader方法返回的是null.因此沒法直接操做引導類加載器,可是咱們能夠根據Class.getClassLoader方法是否爲null判斷這個類是否是引導類加載器加載的,能夠經過下面的方法得到引導類加載器加載的類路徑(每一個jar包或者文件夾對應了一個URL);
sun.misc.Launcher.getBootstrapClassPath().getURLs()
你能夠直接在你的main函數中輸出就能夠了
System.out.println(java.util.Arrays.asList(sun.misc.Launcher.getBootstrapClassPath().getURLs()).toString());
獲得的結果是:
[file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/rt.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/i18n.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/sunrsasign.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/jsse.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/jce.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/charsets.jar,
file:/C:/Program%20Files/Java/j2re1.4.2_10/classes]

其實咱們是能夠指定引導類加載器的類路徑的,java提供了一個-Xbootclasspath參數,不過這個參數不是標準參數。
java -Xbootclasspath: 
運行時指定引導類加載器的加載路徑(jar文件或者目錄)
java -Xbootclasspath/p:
和上面的相同,不過把這個路徑放到原來的路徑前面
java -Xbootclasspath/a:
這個就是在原引導類路徑後面添加類路徑。
上面咱們有提過加載第一個應用類未必就是系統加載器。
若是我把這個應用類的路徑放到引導類路徑中,它將會被引導類加載器加載,大體這樣
java -Xbootclasspath/a:myjar.jar MainClass
若是MainClassmyjar.jar,那麼這個類將會被引導類加載器加載。
若是但願看詳情,使用-verbose參數,爲了看的更清楚,使用重定向,大體爲(windows)
java -verbose -Xbootclasspath/a:myjar.jar MainClass -> C:\out.txt
經過這個參數咱們能夠實現本身的系統類,好比替換掉java.lang.Object的實現,本身能夠擴展
一些方法,不過這樣作彷佛沒有好處,由於那就不是標準了。

咱們最關心的仍是系統類加載器,通常都認爲系統類加載器是加載應用程序第一個類的加載器,
也就是java MainClass命令中加載MainClass的類加載器,這種說法雖然不是很嚴謹,但基本上仍是能夠這樣認爲的,由於咱們不多會改變引導類加載器和擴展類加載器的默認行爲。應該說系統類加載器負責加載classpath路徑中的並且沒有被擴展類加載器加載的類(固然也包括引導類加載器加載的)。若是classpath中有這個類,可是這個類也在擴展類加載器的類路徑,那麼系統類加載器將沒有機會加載它。
咱們不多改變擴展類加載器的行爲,因此通常你本身定義的類都是系統類加載器加載器的。

得到系統類加載器很是簡單,假設MyClass是你定義的一個類
MyClass.class.getClassLoader()
返回的就是系統類加載器,固然這種方法沒法保證絕對正確,咱們可使用更簡單並且必定正確的方式:
ClassLoader.getSystemClassLoader()
得到系統類加載器。咱們知道ClassLoader是一個抽象類,因此係統類加載器確定是ClassLoader的一個子類實現。咱們來看看它是什麼
ClassLoader.getSystemClassLoader().getClass();
結果是class sun.misc.Lancher$AppClassLoader 
能夠看出這是sun的一個實現,從名字能夠看出是一個內部類,目前我也沒有看到這個源代碼,彷佛還不是很清晰:
咱們在看看它的父類是什麼:
ClassLoader.getSystemClassLoader().getClass().getSuperclass();
結果是:class java.net.URLClassLoader
這個是j2se的標準類,它的父類是SecureClassLoader,SecureClassLoader是繼承ClassLoader的。
如今整個關係應該很清楚,咱們會看到幾乎全部的ClassLoader實現都是繼承URLClassLoader的。
由於系統類加載器是很是重要的,並且是咱們能夠直接控制的,因此咱們後面還會介紹,不過先來看一下擴展類
加載器以及它們之間的關係。

擴展類加載器彷佛是一個不起眼的角色,它負責加載java的標準擴展(jre/lib/ext目錄下的全部jar),它其實就是一個普通的加載器,看得見摸得着的。
首先的問題是怎麼知道擴展類加載器在哪裏?
的確沒有直接途徑得到擴展類加載器,可是咱們知道它是系統類加載器的父加載器,咱們已經很容易的得到系統類加載器了,因此咱們能夠間接的得到擴展類加載器:
ClassLoader.getSystemClassLoader().getParent().getClass();
實際上是經過系統類加載器間接的得到了擴展類加載器,看看是什麼東西:
結果是:class sun.misc.Launcher$ExtClassLoader
這個類和系統類加載器同樣是一個內部類,並且定義在同一個類中。
一樣看看它的父類是什麼:
ClassLoader.getSystemClassLoader().getParent().getClass().getSuperclass();
能夠看出結果也是class java.net.URLClassLoader
擴展類加載jre/lib/ext目錄下的全部類,包括jar,目錄下的全部類(目錄名不必定要classes).
如今能夠回答上面的問題了,你寫一個HelloWorld,放到jre/lib/ext/下的某個目錄
好比 jre/lib/ext/myclass/HelloWorld.class
而後在你classpath也設置一份到這個類的路徑,結果執行java HelloWorld時,這個類是被擴展類加載器加載器的,能夠這樣證實
public static void main(String[] args){
System.out.println("loaded by"+HelloWorld.class.getClassLoader().getClass());
System.out.println("Hello World");
}
結果能夠獲得class sun.misc.Launcher$ExtClassLoader
固然若是你把jre/lib/extmyclass這個目錄刪除,仍然能夠運行,可是這樣結果是
class sun.misc.Lancher$AppClassLoader
若是你不知道這個過程的話,假設在你擴展類路徑下有一份classpath中的拷貝,或者是比較低的版本,當你使用新的版本時會發現沒有起做用,知道這個過程你就不會以爲奇怪了。另外就是兩個不一樣的類加載器是能夠加載一個同名的類的,也就是說雖然擴展類加載器加載了某個類,系統類加載器是能夠加載本身的版本的,
可是現有的實現都沒有這樣作,ClassLoader中的方法是會請求父類加載器先加載的,若是你本身定義類加載器徹底能夠修改這種默認行爲,甚至可讓他沒有父加載器。

這裏給出一個方法如何得到擴展類加載器加載的路徑:
    String path=System.getProperty("java.ext.dirs");
        File dir=new File(path);
        if(!dir.exists()||!dir.isDirectory()){
            return Collections.EMPTY_LIST;
        }
        File[] jars=dir.listFiles();
        URL[] urls=new URL[jars.length];
        for(int i=0;i<jars.length;i++){
            urls[i]=sun.misc.URLClassPath.pathToURLs(jars[i].getAbsolutePath())[0];
        }
        return Arrays.asList(urls);

對於擴展類加載器咱們基本上不會去關心,也不多把你本身的jar放到擴展路徑,大部分狀況下咱們都感受不到它的存在,固然若是你必定要放到這個目錄下,必定要知道這個過程,它會優先於classpath中的類。

如今咱們應該很清楚知道某個類是哪一個加載器加載的,而且知道爲何是它加載的,若是要在運行時得到某個類的類加載器,直接使用ClassgetClassLoader()方法就能夠了。

用戶定義的類通常都是系統類加載器加載的,咱們不多直接使用類加載器加載類,咱們甚至不多本身加載類。
由於類在使用時會被自動加載,咱們用到某個類時該類會被自動加載,好比new A()會致使類A自動被加載,不過這種加載只發生一次。
咱們也可使用系統類加載器手動加載類,ClassLoader提供了這個接口
ClassLoader.getSystemClassLoader().loadClass("classFullName");
這就很明確的指定了使用系統類加載器加載指定的類,可是若是該類可以被擴展類加載器加載,系統類加載器仍是不會有機會的。
咱們最經常使用的仍是使用Class.forName加載使用的類,這種方式沒有指定某個特定的ClassLoader,會使用調用類的ClassLoader
也就是說調用這個方法的類的類加載器將會用於加載這個類。好比在類A中使用Class.forName加載類B,那麼加載類A的類加載器將會用於加載類B,這樣兩個類的類加載器是同一個。

最後討論一下如何得到某個類加載器加載了哪些類,這個彷佛有必定的使用價值,能夠看出哪些類被加載了。其實這個也不是很難,由於ClassLoader中有一個classes成員變量就是用來保存類加載器加載的類列表,並且有一個方法
void addClass(Class c) { classes.addElement(c);}
這個方法被JVM調用。
咱們只要利用反射得到classes這個值就能夠了,不過classes聲明爲private,咱們須要修改它的訪問權限(沒有安全管理器時很容易作到)
classes = ClassLoader.class.getDeclaredField("classes");
classes.setAccessible(true);
List ret=(List) classes.get(cl); //classes
是一個Vector
惋惜的是對於引導類加載器沒有辦法得到加載的類,由於它是了的,java中很難控制了。 windows

相關文章
相關標籤/搜索