1. 程式人生 > >java三種類載入器--jre/lib/ext擴充套件jar載入過程

java三種類載入器--jre/lib/ext擴充套件jar載入過程

首先來了解一下位元組碼和class檔案的區別:

我們知道,新建一個java物件的時候,JVM要將這個物件對應的位元組碼載入到記憶體中,這個位元組碼的原始資訊存放在classpath(就是我們新建Java工程的bin目錄下)指定的目錄下的.class檔案,類載入需要將.class檔案匯入到硬碟中,經過一些處理之後變成位元組碼在載入到記憶體中。

下面來看一下簡單的例子:

  1. package com.loadclass.demo;  
  2. import java.util.Date;  
  3. import java.util.List;  
  4. /** 
  5.  * 測試類 
  6.  * @author Administrator
     
  7.  */
  8. publicclass ClassLoaderTest {  
  9.     @SuppressWarnings("rawtypes")  
  10.     publicstaticvoid main(String[] args){  
  11.         //輸出ClassLoaderText的類載入器名稱
  12.         System.out.println("ClassLoaderText類的載入器的名稱:"+ClassLoaderTest.class.getClassLoader().getClass().getName());  
  13.         System.out.println("System類的載入器的名稱:"
    +System.class.getClassLoader());  
  14.         System.out.println("List類的載入器的名稱:"+List.class.getClassLoader());  
  15.         ClassLoader cl = ClassLoaderTest.class.getClassLoader();  
  16.         while(cl != null){  
  17.             System.out.print(cl.getClass().getName()+"->");  
  18.             cl = cl.getParent();  
  19.         }  
  20.         System.out.println(cl);  
  21.     }  
  22. }  
輸出結果:

可以看到,ClassLoaderTest類時由AppClassLoader類載入器載入的。下面就來了解一下JVM中的各個類載入器,同時來解釋一下執行的結果。

Java虛擬機器中類載入器:

Java虛擬機器中可以安裝多個類載入器,系統預設三個主要的類載入器,每個類負責載入特定位置的類:

BootStrap,ExtClassLoader,AppClassLoader

類載入器也是Java類,因為Java類的類載入器本身也是要被類載入器載入的,顯然必須有第一個類載入器不是Java類,這個正是BootStrap,使用C/C++程式碼寫的,已經封裝到JVM核心中了,而ExtClassLoader和AppClassLoader是Java類。

看一下類載入器的屬性結構圖:


Java虛擬機器中的所有類載入器採用具有父子關係的樹形結構進行組織,在例項化每個類載入器物件的時候,需要為其指定一個父級類載入器物件或者預設採用系統類載入器為其父級類載入

類載入器的委託機制:

當Java虛擬機器要載入第一個類的時候,到底派出哪個類載入器去載入呢?

(1). 首先當前執行緒的類載入器去載入執行緒中的第一個類(當前執行緒的類載入器:Thread類中有一個get/setContextClassLoader(ClassLoader cl);方法,可以獲取/指定本執行緒中的類載入器)

(2). 如果類A中引用了類B,Java虛擬機器將使用載入類A的類載入器來載入類B

(3). 還可以直接呼叫ClassLoader.loadClass(String className)方法來指定某個類載入器去載入某個類

每個類載入器載入類時,又先委託給其上級類載入器當所有祖宗類載入器沒有載入到類,回到發起者類載入器,還載入不了,則會丟擲ClassNotFoundException,不是再去找發起者類載入器的兒子,因為沒有getChild()方法。例如:如上圖所示: MyClassLoader->AppClassLoader->Ext->ClassLoader->BootStrap.自定定義的MyClassLoader1首先會先委託給AppClassLoader,AppClassLoader會委託給ExtClassLoader,ExtClassLoader會委託給BootStrap,這時候BootStrap就去載入,如果載入成功,就結束了。如果載入失敗,就交給ExtClassLoader去載入,如果ExtClassLoader載入成功了,就結束了,如果載入失敗就交給AppClassLoader載入,如果載入成功,就結束了,如果載入失敗,就交給自定義的MyClassLoader1類載入器載入,如果載入失敗,就報ClassNotFoundException異常,結束。

對著類載入器的層次結構圖和委託載入原理,解釋先前的執行的結果

因為System類,List,Map等這樣的系統提供jar類都在rt.jar中,所以由BootStrap類載入器載入,因為BootStrap是祖先類,不是Java編寫的,所以打印出class為null

對於ClassLoaderTest類的載入過程,列印結果也是很清楚的。

現在再來做個試驗來驗證上面的結論:

首先將ClassLoaderTest.java打包成.jar檔案(這個步驟就不說了吧,很簡單的)

然後將.jar檔案拷貝到Java的安裝目錄中的Java/jre7/lib/ext/目錄下


這時候你在執行ClassLoaderTest類,結果如下:


這時候就發現了ClassLoaderTest的類載入器變成了ExtClassLoader,這時候就說明了上面的結論是正確的,因為ExtClassLoader載入jre/ext/*.jar,首先AppClassLoader類載入器發請求給ExtClassLoader,然後ExtClassLoader發請求給BootStrap,但是BootStrap沒有找到ClassLoaderTest類,所以交給ExtClassLoader處理,這時候ExtClassLoader在my_lib.jar中找到了ClassLoaderTest類,所以就把它載入了,然後結束了。

其實採用這種樹形的類載入機制的好處就在於:

能夠很好的統一管理類載入,首先交給上級,如果上級有了,就載入,這樣如果之前已經載入過的類,這時候在來載入它的時候只要拿過來用就可以了,無需二次載入了

下面來看一下怎麼定義我們自己的一個類載入器MyClassLoader:

自己可以定義類載入器,要將自己定義的類載入器掛載到系統類載入器樹上,在ClassLoader的構造方法中可以指定parent,沒有指定的話,就使用預設的parent


這裡看一下預設的parent是使用getSystemClassLoader方法獲取的,這個方法的原始碼沒有找到,所以只能通過程式碼來測試一下了

  1. System.out.println("預設的類載入器:"+ClassLoaderTest.class.getClassLoader().getSystemClassLoader());  
輸入結果為:


所以預設的都是將自定義的類載入器掛載到系統類載入器的最低端AppClassLoader,這個也是很合理的。

自定義的類載入器必須繼承抽象類ClassLoader然後重寫findClass方法,其實他內部還有一個loadClass方法和defineClass方法,這兩個方法的作用是:

loadClass方法的原始碼:

  1. public Class<?> loadClass(String name) throws ClassNotFoundException {  
  2.        return loadClass(name, false);  
  3.    }  
再來看一下loadClass(name,false)方法的原始碼:
  1. protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{  
  2.          //加上鎖,同步處理,因為可能是多執行緒在載入類
  3.          synchronized (getClassLoadingLock(name)) {  
  4.              //檢查,是否該類已經載入過了,如果載入過了,就不載入了
  5.              Class c = findLoadedClass(name);  
  6.              if (c == null) {  
  7.                  long t0 = System.nanoTime();  
  8.                  try {  
  9.                      //如果自定義的類載入器的parent不為null,就呼叫parent的loadClass進行載入類
  10.                      if (parent != null) {  
  11.                          c = parent.loadClass(name, false);  
  12.                      } else {  
  13.                          //如果自定義的類載入器的parent為null,就呼叫findBootstrapClass方法查詢類,就是Bootstrap類載入器
  14.                          c = findBootstrapClassOrNull(name);  
  15.                      }  
  16.                  } catch (ClassNotFoundException e) {  
  17.                      // ClassNotFoundException thrown if class not found
  18.                      // from the non-null parent class loader
  19.                  }  
  20.                  if (c == null) {  
  21.                      // If still not found, then invoke findClass in order
  22.                      // to find the class.
  23.                      long t1 = System.nanoTime();  
  24.                      //如果parent載入類失敗,就呼叫自己的findClass方法進行類載入
  25.                      c = findClass(name);  
  26.                      // this is the defining class loader; record the stats
  27.                      sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);  
  28.                      sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);  
  29.                      sun.misc.PerfCounter.getFindClasses().increment();  
  30.                  }  
  31.              }  
  32.              if (resolve) {  
  33.                  resolveClass(c);  
  34.              }  
  35.              return c;  
  36.          }  
  37.      }  
在loadClass程式碼中也可以看到類載入機制的原理,這裡還有這個方法findBootstrapClassOrNull,看一下原始碼:
  1. private Class findBootstrapClassOrNull(String name)  
  2.    {  
  3.        if (!checkName(name)) returnnull;  
  4.        return findBootstrapClass(name);  
  5.    }  

就是檢查一下name是否是否正確,然後呼叫findBootstrapClass方法,但是findBootstrapClass方法是個native本地方法,看不到原始碼了,但是可以猜測是用Bootstrap類載入器進行載入類的,這個方法我們也不能重寫,因為如果重寫了這個方法的話,就會破壞這種委託機制,我們還要自己寫一個委託機制,很是蛋疼的。

defineClass這個方法很簡單就是將class檔案的位元組陣列程式設計一個class物件,這個方法肯定不能重寫,內部實現是在C/C++程式碼中實現的

findClass這個方法就是根據name來查詢到class檔案,在loadClass方法中用到,所以我們只能重寫這個方法了,只要在這個方法中找到class檔案,再將它用defineClass方法返回一個Class物件即可。

這三個方法的執行流程是:每個類載入器:loadClass->findClass->defineClass

前期的知識瞭解後現在就來實現了

首先來看一下需要載入的一個類:ClassLoaderAttachment.java:

  1. package com.loadclass.demo;  
  2. import java.util.Date;