1. 程式人生 > >Java基礎——類載入器

Java基礎——類載入器

Java類載入器的作用就是在執行時載入類。Java類載入器基於三個機制:委託、可見性和單一性。委託機制是指將載入一個類的請求交給父類載入器,如果這個父類載入器不能夠找到或者載入這個類,那麼再載入它。可見性的原理是子類的載入器可以看見所有的父類載入器載入的類,而父類載入器看不到子類載入器載入的類。單一性原理是指僅載入一個類一次,這是由委託機制確保子類載入器不會再次載入父類載入器載入過的類。正確理解類載入器能夠幫你解決NoClassDefFoundError和java.lang.ClassNotFoundException,因為它們和類的載入相關。類載入器通常也是比較高階的Java面試中的重要考題,Java類載入器和工作原理以及classpath如何運作的經常被問到。Java面試題中也經常出現“一個類是否能被兩個不同類載入器載入”這樣的問題。這篇教程中,我們將學到類載入器是什麼,它的工作原理以及一些關於類載入器的知識點。

什麼是類載入器

類載入器是一個用來載入類檔案的類。Java原始碼通過javac編譯器編譯成類檔案。然後JVM來執行類檔案中的位元組碼來執行程式。類載入器負責載入檔案系統、網路或其他來源的類檔案。有三種預設使用的類載入器:Bootstrap類載入器、Extension類載入器和System類載入器(或者叫作Application類載入器)。每種類載入器都有設定好從哪裡載入類。

  • Bootstrap類載入器負責載入rt.jar中的JDK類檔案,它是所有類載入器的父載入器。Bootstrap類載入器沒有任何父類載入器,如果你呼叫String.class.getClassLoader(),會返回null,任何基於此的程式碼會丟擲NUllPointerException異常。Bootstrap載入器被稱為初始類載入器。
  • 而Extension將載入類的請求先委託給它的父載入器,也就是Bootstrap,如果沒有成功載入的話,再從jre/lib/ext目錄下或者java.ext.dirs系統屬性定義的目錄下載入類。Extension載入器由sun.misc.Launcher$ExtClassLoader實現。
  • 第三種預設的載入器就是System類載入器(又叫作Application類載入器)了。它負責從classpath環境變數中載入某些應用相關的類,classpath環境變數通常由-classpath或-cp命令列選項來定義,或者是JAR中的Manifest的classpath屬性。Application類載入器是Extension類載入器的子載入器。通過sun.misc.Launcher$AppClassLoader實現。

除了Bootstrap類載入器是大部分由C來寫的,其他的類載入器都是通過java.lang.ClassLoader來實現的。

總結一下,下面是三種類載入器載入類檔案的地方:

1) Bootstrap類載入器 – JRE/lib/rt.jar

2) Extension類載入器 – JRE/lib/ext或者java.ext.dirs指向的目錄

3) Application類載入器 – CLASSPATH環境變數, 由-classpath或-cp選項定義,或者是JAR中的Manifest的classpath屬性定義.

類載入器的工作原理

我之前已經提到過了,類載入器的工作原理基於三個機制:委託、可見性和單一性。這一節,我們來詳細看看這些規則,並用一個例項來理解工作原理。下面顯示的是類載入器使用委託機制的工作原理。

委託機制

當一個類載入和初始化的時候,類僅在有需要載入的時候被載入。假設你有一個應用需要的類叫作Abc.class,首先載入這個類的請求由Application類載入器委託給它的父類載入器Extension類載入器,然後再委託給Bootstrap類載入器。Bootstrap類載入器會先看看rt.jar中有沒有這個類,因為並沒有這個類,所以這個請求由回到Extension類載入器,它會檢視jre/lib/ext目錄下有沒有這個類,如果這個類被Extension類載入器找到了,那麼它將被載入,而Application類載入器不會載入這個類;而如果這個類沒有被Extension類載入器找到,那麼再由Application類載入器從classpath中尋找。記住classpath定義的是類檔案的載入目錄,而PATH是定義的是可執行程式如javac,java等的執行路徑。

可見性機制

根據可見性機制,子類載入器可以看到父類載入器載入的類,而反之則不行。所以下面的例子中,當Abc.class已經被Application類載入器載入過了,然後如果想要使用Extension類載入器載入這個類,將會丟擲java.lang.ClassNotFoundException異常。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

package test;

import java.util.logging.Level;

import java.util.logging.Logger;

/**

* Java program to demonstrate How ClassLoader works in Java,

* in particular about visibility principle of ClassLoader.

*

* @author Javin Paul

*/

public class ClassLoaderTest {

public static void main(String args[]) {

try {         

//printing ClassLoader of this class

System.out.println("ClassLoaderTest.getClass().getClassLoader() : "

+ ClassLoaderTest.class.getClassLoader());

//trying to explicitly load this class again using Extension class loader

Class.forName("test.ClassLoaderTest", true

,  ClassLoaderTest.class.getClassLoader().getParent());

} catch (ClassNotFoundException ex) {

Logger.getLogger(ClassLoaderTest.class.getName()).log(Level.SEVERE, null, ex);

}

}

}

輸出:

1

2

3

4

5

6

7

8

9

10

11

12

13

ClassLoaderTest.getClass().getClassLoader() : [email protected]

16/08/2012 2:43:48 AM test.ClassLoaderTest main

SEVERE: null

java.lang.ClassNotFoundException: test.ClassLoaderTest

at java.net.URLClassLoader$1.run(URLClassLoader.java:202)

at java.security.AccessController.doPrivileged(Native Method)

at java.net.URLClassLoader.findClass(URLClassLoader.java:190)

at sun.misc.Launcher$ExtClassLoader.findClass(Launcher.java:229)

at java.lang.ClassLoader.loadClass(ClassLoader.java:306)

at java.lang.ClassLoader.loadClass(ClassLoader.java:247)

at java.lang.Class.forName0(Native Method)

at java.lang.Class.forName(Class.java:247)

at test.ClassLoaderTest.main(ClassLoaderTest.java:29)

單一性機制

根據這個機制,父載入器載入過的類不能被子載入器載入第二次。雖然重寫違反委託和單一性機制的類載入器是可能的,但這樣做並不可取。你寫自己的類載入器的時候應該嚴格遵守這三條機制。

如何顯式的載入類

Java提供了顯式載入類的API:Class.forName(classname)和Class.forName(classname, initialized, classloader)。就像上面的例子中,你可以指定類載入器的名稱以及要載入的類的名稱。類的載入是通過呼叫java.lang.ClassLoader的loadClass()方法,而loadClass()方法則呼叫了findClass()方法來定位相應類的位元組碼。在這個例子中Extension類載入器使用了java.net.URLClassLoader,它從JAR和目錄中進行查詢類檔案,所有以”/”結尾的查詢路徑被認為是目錄。如果findClass()沒有找到那麼它會丟擲java.lang.ClassNotFoundException異常,而如果找到的話則會呼叫defineClass()將位元組碼轉化成類例項,然後返回。

什麼地方使用類載入器

類載入器是個很強大的概念,很多地方被運用。最經典的例子就是AppletClassLoader,它被用來載入Applet使用的類,而Applets大部分是在網上使用,而非本地的作業系統使用。使用不同的類載入器,你可以從不同的源地址載入同一個類,它們被視為不同的類。J2EE使用多個類載入器載入不同地方的類,例如WAR檔案由Web-app類載入器載入,而EJB-JAR中的類由另外的類載入器載入。有些伺服器也支援熱部署,這也由類載入器實現。你也可以使用類載入器來載入資料庫或者其他持久層的資料。

以上是關於類載入器的工作原理。我們已經知道了委託、可見性以及單一性原理,這些對於除錯類載入器相關問題時至關重要。這些對於Java程式設計師和架構師來說都是必不可少的知識。