java之classloader (2)

來源:互聯網
上載者:User
這篇文章將比較深入的介紹java類載入原理和過程,雖然很多情況你都不需要瞭解類載入的過程,但是如果你希望瞭解java是如何工作的,或者希望定義自己的類載入器,就一定要瞭解類載入的過程,當然,無論你是否要
參與類載入過程,瞭解這個過程對你都會有很大的協助。本文希望通過一步一步的分析來說明類載入的過程,希望這種分析方法也會對你有某些協助。本文使用的JDK是sun的jdk_1.4.2版本,當然這裡重在闡述原理,和JDK本身沒有多大的關係。這篇文章試圖解決下面一些問題: 類載入原理
引導類載入器,擴充類載入器和系統類別載入器
如果知道某個類是哪個類載入器載入的
如果得到系統類別載入器載入了那些類

 

首先我們要分析類載入原理,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函數中輸出就可以了Java代碼
  1. System.out.println(java.util.Arrays.asList(sun.misc.Launcher.getBootstrapClassPath().getURLs()).toString());  
System.out.println(java.util.Arrays.asList(sun.misc.Launcher.getBootstrapClassPath().getURLs()).toString());

 

得到的結果是:

 

其實我們是可以指定引導類載入器的類路徑的,java提供了一個-Xbootclasspath參數,不過這個參數不是標準參數。java -Xbootclasspath: 運行時指定引導類載入器的載入路徑(jar檔案或者目錄)java -Xbootclasspath/p:和上面的相同,不過把這個路徑放到原來的路徑前面java -Xbootclasspath/a:這個就是在原引導類路徑後面添加類路徑。上面我們有提過載入第一個應用類未必就是系統載入器。如果我把這個應用類的路徑放到引導類路徑中,它將會被引導類載入器載入,大致這樣
java -Xbootclasspath/a:myjar.jar MainClass
如果MainClass在myjar.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()返回的就是系統類別載入器,當然這種方法無法保證絕對正確,我們可以使用更簡單而且一定正確的方式:Java代碼
  1. ClassLoader.getSystemClassLoader(); 
    //獲得系統類別載入器。
      
ClassLoader.getSystemClassLoader(); //獲得系統類別載入器。

 

我們知道ClassLoader是一個抽象類別,所以系統類別載入器肯定是ClassLoader的一個子類實現。我們來看看它是什麼Java代碼
  1. ClassLoader.getSystemClassLoader().getClass(); 
    //結果是class sun.misc.Lancher$AppClassLoader
      
ClassLoader.getSystemClassLoader().getClass(); //結果是class sun.misc.Lancher$AppClassLoader

 

 可以看出這是sun的一個實現,從名字可以看出是一個內部類,目前我也沒有看到這個原始碼,似乎還不是很清晰:
我們在看看它的父類是什麼:Java代碼
  1. ClassLoader.getSystemClassLoader().getClass().getSuperclass();   
ClassLoader.getSystemClassLoader().getClass().getSuperclass(); 

 

結果是:class java.net.URLClassLoader , 這個是j2se的標準類,它的父類是SecureClassLoader,而SecureClassLoader是繼承ClassLoader的。現在整個關係應該很清楚,我們會看到幾乎所有的ClassLoader實現都是繼承URLClassLoader的。
因為系統類別載入器是非常重要的,而且是我們可以直接控制的,所以我們後面還會介紹,不過先來看一下擴充類
載入器以及它們之間的關係。
擴充類載入器似乎是一個不起眼的角色,它負責載入java的標準擴充(jre/lib/ext目錄下的所有jar),它其實就是一個普通的載入器,看得見摸得著的。首先的問題是怎麼知道擴充類載入器在哪裡?
的確沒有直接途徑獲得擴充類載入器,但是我們知道它是系統類別載入器的父載入器,我們已經很容易的獲得系統類別載入器了,所以我們可以間接的獲得擴充類載入器:

 

Java代碼
  1. ClassLoader.getSystemClassLoader().getParent().getClass();   
ClassLoader.getSystemClassLoader().getParent().getClass(); 

 

其實是通過系統類別載入器間接的獲得了擴充類載入器,看看是什麼東西:結果是:class sun.misc.Launcher$ExtClassLoader這個類和系統類別載入器一樣是一個內部類,而且定義在同一個類中。
同樣看看它的父類是什麼:Java代碼
  1. ClassLoader.getSystemClassLoader().getParent().getClass().getSuperclass();   
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時,這個類是被擴充類載入器載入器的,可以這樣證明Java代碼
  1. public
     
    static
     
    void
     main(String[] args){  
  2.     System.out.println("loaded by"
    +HelloWorld.
    class
    .getClassLoader().getClass());  
  3.     System.out.println("Hello World"
    );  
  4. }  
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/ext下myclass這個目錄刪除,仍然可以運行,但是這樣結果是
class sun.misc.Lancher$AppClassLoader如果你不知道這個過程的話,假設在你擴充類路徑下有一份classpath中的拷貝,或者是比較低的版本,當你使用新的版本時會發現沒有起作
用,知道這個過程你就不會覺得奇怪了。另外就是兩個不同的類載入器是可以載入一個同名的類的,也就是說雖然擴充類載入器載入了某個類,系統類別載入器是可以
載入自己的版本的,
但是現有的實現都沒有這樣做,ClassLoader中的方法是會請求父類載入器先載入的,如果你自己定義類載入器完全可以修改這種預設行為,甚至可以讓
他沒有父載入器。
這裡給出一個方法如何獲得擴充類載入器載入的路徑: Java代碼
  1. String path=System.getProperty(
    "java.ext.dirs"
    );  
  2.    File dir=new
     File(path);  
  3.    if
    (!dir.exists()||!dir.isDirectory()){  
  4.        return
     Collections.EMPTY_LIST;  
  5.    }  
  6.    File[] jars=dir.listFiles();  
  7.    URL[] urls=new
     URL[jars.length];  
  8.    for
    (
    int
     i=
    0
    ;i<jars.length;i++){  
  9.        urls[i]=sun.misc.URLClassPath.pathToURLs(jars[i].getAbsolutePath())[0
    ];  
  10.    }  
  11.    return
     Arrays.asList(urls);  
     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中的類。現在我們應該很清楚知道某個類是哪個載入器載入的,並且知道為什麼是它載入的,如果要在運行時獲得某個類的類載入器,直接使用Class的getClassLoader()方法就可以了。
使用者定義的類一般都是系統類別載入器載入的,我們很少直接使用類載入器載入類,我們甚至很少自己載入類。
因為類在使用時會被自動載入,我們用到某個類時該類會被自動載入,比如new A()會導致類A自動被載入,不過這種載入只發生一次。我們也可以使用系統類別載入器手動載入類,ClassLoader提供了這個介面Java代碼
  1. ClassLoader.getSystemClassLoader().loadClass(
    "classFullName"
    );   
ClassLoader.getSystemClassLoader().loadClass("classFullName"); 

 

這就很明確的指定了使用系統類別載入器載入指定的類,但是如果該類能夠被擴充類載入器載入,系統類別載入器還是不會有機會的。我們最常用的還是使用Class.forName載入使用的類,這種方式沒有指定某個特定的ClassLoader,會使用調用類的ClassLoader。
也就是說調用這個方法的類的類載入器將會用於載入這個類。比如在類A中使用Class.forName載入類B,那麼載入類A的類載入器將會用於載入類B,這樣兩個類的類載入器是同一個。最後討論一下如何獲得某個類載入器載入了哪些類,這個似乎有一定的使用價值,可以看出哪些類被載入了。其實這個也不是很難,因為ClassLoader中有一個classes成員變數就是用來儲存類載入器載入的類列表,而且有一個方法Java代碼
  1. void
     addClass(Class c) { classes.addElement(c);}   
void addClass(Class c) { classes.addElement(c);} 

 

這個方法被JVM調用。
我們只要利用反射獲得classes這個值就可以了,不過classes聲明為private的,我們需要修改它的存取權限(沒有安全管理器時很容易做到)Java代碼
  1. classes = ClassLoader.
    class
    .getDeclaredField(
    "classes"
    );  
  2. classes.setAccessible(true
    );  
  3. List ret=(List) classes.get(cl); //classes是一個Vector
      
classes = ClassLoader.class.getDeclaredField("classes");classes.setAccessible(true);List ret=(List) classes.get(cl); //classes是一個Vector

 

可惜的是對於引導類載入器沒有辦法獲得載入的類,因為它是c實現的,在java中很難控制了。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.