https://www.ibm.com/developerworks/cn/java/j-lo-classloader/java
用來加載 Java 類到 Java 虛擬機中數據庫
Java 源程序(.java 文件)在通過 Java 編譯器編譯以後就被轉換成 Java 字節代碼(.class 文件)。類加載器負責讀取 Java 字節代碼,並轉換成 java.lang.Class
類的一個實例;每一個這樣的實例用來表示一個 Java 類。經過此實例的 newInstance()
方法就能夠建立出該類的一個對象。apache
基本上全部的類加載器都是 java.lang.ClassLoader
類的一個實例。bootstrap
java.lang.ClassLoader
類java.lang.Class
類的一個實例;方法 | 說明 |
---|---|
getParent() |
返回該類加載器的父類加載器。 |
loadClass(String name) |
加載名稱爲 name 的類,返回的結果是 java.lang.Class 類的實例。 |
findClass(String name) |
查找名稱爲 name 的類,返回的結果是 java.lang.Class 類的實例。 |
findLoadedClass(String name) |
查找名稱爲 name 的已經被加載過的類,返回的結果是 java.lang.Class 類的實例。 |
defineClass(String name, byte[] b, int off, int len) |
把字節數組 b 中的內容轉換成 Java 類,返回的結果是 java.lang.Class 類的實例。這個方法被聲明爲 final 的。 |
resolveClass(Class<?> c) |
連接指定的 Java 類。 |
表示類名稱的 name
參數的值是類的二進制名稱。須要注意的是內部類的表示,如 com.example.Sample$1
和 com.example.Sample$Inner
等表示方式。數組
Java 中的類加載器大體能夠分紅兩類,一類是系統提供的,另一類則是由 Java 應用開發人員編寫的。安全
引導類加載器(bootstrap class loader):它用來加載 Java 的核心庫,是用原生代碼來實現的,並不繼承自 java.lang.ClassLoader
。網絡
擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。工具
系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。通常來講,Java 應用的類都是由它來完成加載的。能夠經過 ClassLoader.getSystemClassLoader()
來獲取它。spa
java.lang.ClassLoader
類的方式實現本身的類加載器,以知足一些特殊的需求。 除了引導類加載器以外,全部的類加載器都有一個父類加載器。經過 getParent()
方法能夠獲得。線程
對於系統提供的類加載器來講,系統類加載器的父類加載器是擴展類加載器,而擴展類加載器的父類加載器是引導類加載器;
對於開發人員編寫的類加載器來講,其父類加載器是加載此類加載器 Java 類的類加載器。
由於類加載器 Java 類如同其它的 Java 類同樣,也是要由類加載器來加載的。通常來講,開發人員編寫的類加載器的父類加載器是系統類加載器。
public class ClassLoaderTree { public static void main(String[] args) { ClassLoader loader = ClassLoaderTree.class.getClassLoader(); //有些 JDK 的實現對於父類加載器是引導類加載器的狀況,getParent()方法返回 null while (loader != null) { System.out.println(loader.toString()); loader = loader.getParent(); } } } //輸出 sun.misc.Launcher$AppClassLoader@9304b1 //系統類加載器實例 sun.misc.Launcher$ExtClassLoader@190d11 //擴展類加載器實例
類加載器在嘗試本身去查找某個類的字節代碼並定義它時,會先代理給其父類加載器,由父類加載器先去嘗試加載這個類,依次類推。
Java 虛擬機不只要看類的全名是否相同,還要看加載此類的類加載器(定義加載器)是否同樣。只有二者都相同的狀況,才認爲兩個類是相同的
經過代理模式,對於 Java 核心庫的類的加載工做由引導類加載器來統一完成,保證了 Java 應用所使用的都是同一個版本的 Java 核心庫的類,是互相兼容的。
不一樣類加載器加載的類之間是不兼容的,這就至關於在 Java 虛擬機內部建立了一個個相互隔離的 Java 類空間。
加載 + 啓動加載
defineClass
來實現的;而啓動類的加載工過程是經過調用加載器的 loadClass
來實現的。前者稱爲一個類的定義加載器(defining loader),後者稱爲初始加載器(initiating loader)。
在 Java 虛擬機判斷兩個類是否相同的時候,使用的是類的定義加載器。
類 java.lang.Thread
中的方法 getContextClassLoader()
和 setContextClassLoader(ClassLoader cl)
用來獲取和設置線程的上下文類加載器。
若是沒有經過 setContextClassLoader(ClassLoader cl)
方法進行設置的話,線程將繼承其父線程的上下文類加載器。
Java 應用運行的初始線程的上下文類加載器是系統類加載器。在線程中運行的代碼能夠經過此類加載器來加載類和資源。
該方法有兩種形式:
Class.forName(String name, boolean initialize, ClassLoader loader)
和
Class.forName(String className)
。
第一種形式的參數 name
表示的是類的全名;initialize
表示是否初始化類;loader
表示加載時使用的類加載器。
第二種形式則至關於設置了參數 initialize
的值爲 true
,loader
的值爲當前類的類加載器。
Class.forName
的一個很常見的用法是在加載數據庫驅動的時候
如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance() //
用來加載 Apache Derby 數據庫的驅動。
文件系統類加載器
加載存儲在文件系統上的 Java 字節代碼,經過 類 FileSystemClassLoader 的
defineClass()
方法來把這些字節代碼轉換成 java.lang.Class
類的實例。
類 NetworkClassLoader
負責經過網絡下載 Java 類字節代碼並定義出 Java 類。
每一個 Web 應用都有一個對應的類加載器實例。
該類加載器也使用代理模式,所不一樣的是它是首先嚐試去加載某個類,若是找不到再代理給父類加載器。這與通常類加載器的順序是相反的。這是 Java Servlet 規範中的推薦作法,其目的是使得 Web 應用本身的類的優先級高於 Web 容器提供的類。
這種代理模式的一個例外是:Java 核心庫的類是不在查找範圍以內的。這也是爲了保證 Java 核心庫的類型安全。
每一個 Web 應用本身的 Java 類文件和使用的庫的 jar 包,分別放在 WEB-INF/classes
和 WEB-INF/lib
目錄下面。
多個應用共享的 Java 類文件和 jar 包,分別放在 Web 容器指定的由全部 Web 應用共享的目錄下面。
當出現找不到類的錯誤時,檢查當前類的類加載器和當前線程的上下文類加載器是否正確。
OSGi™是 Java 上的動態模塊系統。它爲開發人員提供了面向服務和基於組件的運行環境,並提供標準的方式用來管理軟件的生命週期。
OSGi 中的每一個模塊都有對應的一個類加載器。它負責加載模塊本身包含的 Java 包和類。當它須要加載 Java 核心庫的類時(以 java
開頭的包和類),它會代理給父類加載器(一般是啓動類加載器)來完成。當它須要加載所導入的 Java 類時,它會代理給導出此 Java 類的模塊來完成加載。
若是一個類庫只有一個模塊使用,把該類庫的 jar 包放在模塊中,在 Bundle-ClassPath
中指明便可。
若是一個類庫被多個模塊共用,能夠爲這個類庫單獨的建立一個模塊,把其它模塊須要用到的 Java 包聲明爲導出的。其它模塊聲明導入這些類。
若是類庫提供了 SPI 接口,而且利用線程上下文類加載器來加載 SPI 實現的 Java 類,有可能會找不到 Java 類。若是出現了 NoClassDefFoundError
異常,首先檢查當前線程的上下文類加載器是否正確。經過 Thread.currentThread().getContextClassLoader()
就能夠獲得該類加載器。該類加載器應該是該模塊對應的類加載器。若是不是的話,能夠首先經過 class.getClassLoader()
來獲得模塊對應的類加載器,再經過 Thread.currentThread().setContextClassLoader()
來設置當前線程的上下文類加載器。