Java類加載器工作原理

Java類加載器是用來在運行時加載(*.class文件)。Java類加載器基於三個原則:委託、可見性、唯一性。委託原則把加載類的請求轉發給父 類加載器,而且僅加載類當父 類加載器無法找到或者不能加載類時。可見性原則允許子類加載器查看由父類加載器加載的所有的類,但是父類加載器不能查看由子類加載器加載的類。唯一性原則只允許加載一次類文件,這基本上是通過委託原則來實現的並確保子類加載器不重新加載由父類加載器加載過的類。正確的理解類加載器原理必須解決像 NoClassDefFoundError in Java、 java.lang.ClassNotFoundException(這些現象和加載類有關係)的問題。類加載器在高級Java面試中也是一個重要的課題,在此場合中對類加載器和類路徑的工作原理有一定的瞭解是Java程序員所預期的。我在各種Java面試中經常遇見這樣的問題:在Java中一個類可以被兩個不同的類加載器加載嗎?在本文中,我們將學習什麼是Java中的類加載器、它的工作原理以及與它有關的一些細節。

Java的類加載器是什麼

Java中的類加載器是加載Java類文件(*.class)的一個類。Java代碼被javac 編譯器編譯後以字節碼的形式保存到類文件,JVM(Java虛擬機)通過操作類文件裏的字節碼來執行Java程序。類加載器負責從文件系統、網絡或任何其它資源中加載類文件。Java中使用的默認類加載器有以下三種:Bootstrap , Extension 以及 System or Application class loader 。每個類加載器都有一個預定義的位置,它們在那裏加載類文件。Bootstrap 類加載器負責從rt.jar中加載標準JDK類文件,並且它是Java中所有類加載器的父級。Bootstrap類加載器沒有任何父級,如果你調用String.class.getClassLoader() 則返回null 而且與此相關的代碼則拋出空指針異常。Bootstrap類加載器在java中也被稱爲Primordial ClassLoader(原生類加載器)Extension 類加載器委託它的父級Bootstrap類加載器來加載類文件,如果委託失敗,則從指向java.ext.dirs 系統屬性的jre/lib/ext目錄或者任何其它目錄加載類文件。JVM中的Extension 類加載器被sun.misc.Launcher$ExtClassLoader實現。 JVM 使用的第三種默認類加載器就是System or Application class loader ,它主要負責從CLASSPATH環境變量中加載應用程序特定的類文件,-classpath or -cp 命令行選項, JAR內部清單文件的類路徑屬性。Application類加載器是Extension 類加載器的子級,它由sun.misc.Launcher$AppClassLoader類實現而且,除Bootstrap 類加載器(大都由C 語言實現)以外,所有的Java類加載器使用java.lang.ClassLoader 來實現。

簡而言之,這三種類加載器加載類文件的路徑如下:

1) Bootstrap ClassLoader - JRE/lib/rt.jar

2) Extension ClassLoader - JRE/lib/ext 或者任何指向java.ext.dirs的路徑

3) Application ClassLoader - CLASSPATH環境變量、-classpath or -cp 命令行選項,

JAR內部清單文件的類路徑屬性

Java類加載器工作原理

正如我前面所描述的那樣,java類加載器基於三個原則:委託、可見性、唯一性。在本節中我們將看到這些原則的細節之處以及通過示例來理解java類加載器工作原理。順便說一句,這裏有一個關係圖,它解釋了在java中通過使用委託類加載器如何加載類文件。

委託原則

在探討java中的類文件在什麼時候被加載和初始化的問題時,java中的類文件在它被需要的時候被加載。假設您有一個叫做Abc.class 的應用程序特定的類文件,加載這個類文件的第一個請求發送到 Application類加載器。Application類加載器委託它的父級Extension 類加載器,而Extension 類加載器委託給Bootstrap類加載器。Bootstrap類加載器在rt.jar 中尋找類文件,由於沒有找到,請求轉發到Extension 類加載器。Extension 類加載器在jre/lib/ext目錄中尋找類文件並嘗試加載類文件,如果找到了類文件,這時Extension 類加載器將會加載類文件(Application類加載器將永遠不會加載類文件);但是如果Extension 類加載器沒有加載類文件,那麼Application類加載器將會從java類路徑中加載類文件。請注意:類路徑用於加載類文件,而路徑用來查找可執行的像javac or java 的命令。

可見性原則

根據可見性原則,子級類加載器可以看到父級類加載器加載的類文件,但是反過來則行不通。這意味着,假如Application類加載器加載了Abc.class 文件,那麼嘗試使用Extension 類加載器去加載Abc.class 文件,則會拋出異常java.lang.ClassNotFoundException。請看下面的示例:

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);
}
}

}

Output:
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)

唯一性原則

根據這個原則,一個類文件被父級類加載器加載後,子級類加載器則不能加載它。儘管完全可以編寫一個類加載器,它自己加載類文件,這違反了委託和唯一性原則,這樣做沒有什麼好處。在您編寫您自己的類加載器時,您應當遵守所有的類加載器原則。

java中如何準確加載類
java提供了API通過Class.forName(classname) 和Class.forName(classname, initialized, classloader)方法來明確加載一個類,還記得JDBC中用來加載JDBC驅動的方式嗎,就是使用該機制來加載對應的類。就像我們在上邊例子中展示的一樣,你可以將加載特定類的加載器的名稱連同類的二進制名稱一起作爲參數傳進來。Class是通過調用java.lang.ClassLoader的loadClass()方法,該方法又會調用findClass()方法來爲相應類查找對應的字節碼。在這個例子中擴展類加載器使用java.lang.URLClassLoader用來在jar包和目錄中查找類文件以及資源。其中,查詢中所有以"/"結尾的的都會被當作目錄處理。如果findClass()方法沒有找到相關類,那麼它會拋出java.lang.ClassNotFoundException異常;如果該方法找到了相關類,它會調用defineClass()方法來將字節碼轉換爲.class的實例並將該實例返回給調用者。

java中在裏使用類加載器
在Java中類加載器是一個強有力的概念,在許多地方都有使用。其中比較流行的一個例子就是applet中用來加載類的AppletClassLoader類加載器,由於applet是從互聯網上加載而不是從本地文件系統中來加載類。通過使用單獨的類加載器可以從多個不同的資源中加載多次相同的類,並且這些類在JVM中會被當作不同的類對待。J2EE使用不同的類加載器從不同的位置來加載類,比如war包中的類是通過Web-app類加載器來加載,而在EJB-JAR包中綁定的類則通過其他的類加載器加載。而一些web服務器也支持熱部署功能,這都是通過實現ClassLoader來達到目的的。同時也可以使用ClassLoader來從數據庫或者其他持久化存儲系統中來加載類。
這就是所有關於java中的類加載器及其工作原理。我們已經瞭解了委託機制、可見性、唯一性原則,這些對代碼調試或者解決java中類加載器相關問題時都很重要。總而言之,類加載器工作原理對每一位設計java應用和包的開發者和架構師所必備的知識。


1. 本文由程序員學架構摘

2. 本文譯自http://javarevisited.blogspot.com/2012/12/how-classloader-works-in-java.html?m=1

3. 轉載請務必註明本文出自程序員學架構(微信號:archleaner )

4. 更多文章請掃碼:



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