018.Java類加載器

https://www.ibm.com/developerworks/cn/java/j-lo-classloader/java

 

類加載器(class loader)

用來加載 Java 類到 Java 虛擬機中數據庫

  • 通常來講,Java 虛擬機使用 Java 類的方式以下:

  Java 源程序(.java 文件)在通過 Java 編譯器編譯以後就被轉換成 Java 字節代碼(.class 文件)。類加載器負責讀取 Java 字節代碼,並轉換成 java.lang.Class類的一個實例;每一個這樣的實例用來表示一個 Java 類。經過此實例的 newInstance()方法就能夠建立出該類的一個對象。apache

 

  • 實際的狀況可能更加複雜,好比 Java 字節代碼多是經過工具動態生成的,也多是經過網絡下載的

  基本上全部的類加載器都是 java.lang.ClassLoader類的一個實例。bootstrap

 

java.lang.ClassLoader

  • 根據一個指定的類的名稱,找到或者生成其對應的字節代碼,而後從這些字節代碼中定義出一個 Java 類,即 java.lang.Class類的一個實例;

表示類名稱的 name參數的值是類的二進制名稱。須要注意的是內部類的表示,如 com.example.Sample$1和 com.example.Sample$Inner等表示方式。數組

 

  • 負責加載 Java 應用所需的資源,如圖像文件和配置文件等。

 

 

類加載器的樹狀組織結構

Java 中的類加載器大體能夠分紅兩類,一類是系統提供的,另一類則是由 Java 應用開發人員編寫的。安全

 

  • 系統提供的類加載器主要有下面3個:

  引導類加載器(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 應用所使用的都是同一個版本的 Java 核心庫的類,是互相兼容的。

  • 相同名稱的類能夠並存在 Java 虛擬機中,只須要用不一樣的類加載器來加載它們便可。

不一樣類加載器加載的類之間是不兼容的,這就至關於在 Java 虛擬機內部建立了一個個相互隔離的 Java 類空間。

 

加載類的過程

加載 + 啓動加載

  • 真正完成類的加載工做是經過調用加載器的 defineClass來實現的;而啓動類的加載工過程是經過調用加載器的 loadClass來實現的。

  前者稱爲一個類的定義加載器(defining loader),後者稱爲初始加載器(initiating loader)。

  在 Java 虛擬機判斷兩個類是否相同的時候,使用的是類的定義加載器。

 

  • 對於一個類加載器實例來講,相同全名的類只加載一次

 

 

線程上下文類加載器

  類 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用來獲取和設置線程的上下文類加載器。

  若是沒有經過 setContextClassLoader(ClassLoader cl)方法進行設置的話,線程將繼承其父線程的上下文類加載器。

  Java 應用運行的初始線程的上下文類加載器是系統類加載器。在線程中運行的代碼能夠經過此類加載器來加載類和資源。

 

  • 在 SPI 接口的代碼中使用線程上下文類加載器,就能夠成功的加載到 SPI 實現的類。

 

一種加載類的方法 Class.forName

該方法有兩種形式:

Class.forName(String name, boolean initialize, ClassLoader loader)

Class.forName(String className)

第一種形式的參數 name表示的是類的全名;initialize表示是否初始化類;loader表示加載時使用的類加載器。

第二種形式則至關於設置了參數 initialize的值爲 trueloader的值爲當前類的類加載器。

 

Class.forName的一個很常見的用法是在加載數據庫驅動的時候

如 Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()  //用來加載 Apache Derby 數據庫的驅動。

 

 

開發本身的類加載器

  • 文件系統類加載器

  加載存儲在文件系統上的 Java 字節代碼,經過 類 FileSystemClassLoader 的 defineClass()方法來把這些字節代碼轉換成 java.lang.Class類的實例。

  • 網絡類加載器

  類 NetworkClassLoader負責經過網絡下載 Java 類字節代碼並定義出 Java 類。

 

 

類加載器與 Web 容器

  • 運行在 Java EE™容器中的 Web 應用來講,類加載器的實現方式與通常的 Java 應用有所不一樣

  每一個 Web 應用都有一個對應的類加載器實例。

  該類加載器也使用代理模式,所不一樣的是它是首先嚐試去加載某個類,若是找不到再代理給父類加載器。這與通常類加載器的順序是相反的。這是 Java Servlet 規範中的推薦作法,其目的是使得 Web 應用本身的類的優先級高於 Web 容器提供的類。

  這種代理模式的一個例外是:Java 核心庫的類是不在查找範圍以內的。這也是爲了保證 Java 核心庫的類型安全。

 

  • 絕大多數狀況下,Web 應用的開發人員不須要考慮與類加載器相關的細節。下面給出幾條簡單的原則:

每一個 Web 應用本身的 Java 類文件和使用的庫的 jar 包,分別放在 WEB-INF/classes和 WEB-INF/lib目錄下面。

多個應用共享的 Java 類文件和 jar 包,分別放在 Web 容器指定的由全部 Web 應用共享的目錄下面。

當出現找不到類的錯誤時,檢查當前類的類加載器和當前線程的上下文類加載器是否正確。

 

 

類加載器與 OSGi

OSGi™是 Java 上的動態模塊系統。它爲開發人員提供了面向服務和基於組件的運行環境,並提供標準的方式用來管理軟件的生命週期。

  • OSGi 中的每一個模塊(bundle)都包含 Java 包和類

OSGi 中的每一個模塊都有對應的一個類加載器。它負責加載模塊本身包含的 Java 包和類。當它須要加載 Java 核心庫的類時(以 java開頭的包和類),它會代理給父類加載器(一般是啓動類加載器)來完成。當它須要加載所導入的 Java 類時,它會代理給導出此 Java 類的模塊來完成加載。

 

  • 面提供幾條比較好的建議

若是一個類庫只有一個模塊使用,把該類庫的 jar 包放在模塊中,在 Bundle-ClassPath中指明便可。

若是一個類庫被多個模塊共用,能夠爲這個類庫單獨的建立一個模塊,把其它模塊須要用到的 Java 包聲明爲導出的。其它模塊聲明導入這些類。

若是類庫提供了 SPI 接口,而且利用線程上下文類加載器來加載 SPI 實現的 Java 類,有可能會找不到 Java 類。若是出現了 NoClassDefFoundError異常,首先檢查當前線程的上下文類加載器是否正確。經過 Thread.currentThread().getContextClassLoader()就能夠獲得該類加載器。該類加載器應該是該模塊對應的類加載器。若是不是的話,能夠首先經過 class.getClassLoader()來獲得模塊對應的類加載器,再經過 Thread.currentThread().setContextClassLoader()來設置當前線程的上下文類加載器。

相關文章
相關標籤/搜索