java類加載器的工作原理剖析

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

1.什麼是類加載器

類加載器是一個用來加載類文件的類。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屬性定義.


2. 類加載器的工作原理

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

委託機制

當一個類加載和初始化的時候,類僅在有需要加載的時候被加載。假設你有一個應用需要的類叫作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() : sun.misc.Launcher$AppClassLoader@601bb1
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)

單一性機制

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

3. 如何顯式的加載類

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程序員和架構師來說都是必不可少的知識。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章