類加載器淺談

  1、基本概念

        顧名思義,類加載器(class loader)用來加載java類到java虛擬機中。一般來說,java虛擬機使用java類的方式如下:java源程序(.java文件)在經過java編譯器編譯之後就被轉換成java字節代碼(.class文件)。類加載器負責讀取java字節代碼,並轉換成java.lang.Class類的一個實例。每個這樣的實力用來表示一個java類。通過此實例的newInstance()方法就可以創建一個該類的對象。實際的情況可能更復雜,比如java字節代碼克鞥是通過工具動態自動生成的,也可能是通過網絡下載的。基本上,所有的類加載器都是java.lang.ClassLoader類的一個實例。

  2、java.lang.ClassLoader類介紹

        java.lang.ClassLoader類的基本職責就是根據一個指定的類的名稱,找到或者生成器對應的字節碼,然後從這些字節代碼中定義出一個java類,即java.lang.Class類的一個實例。初次之外,ClassLoader還負責加載java應用所需的資源,如圖像文件和配置文件等。爲了完成加載類的這個職責,ClassLoader提供了一系列的方法,比較重要的方法如表一所示。

表1.ClassLoader中與加載類相關的方法
方法 說明
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類


對於表1中給出的方法,表示類名稱的name參數的值是類的binary name(二進制名稱)。需要注意的是內部類的表示,如com.example.Good表示方式。

  3、類加載器的樹狀組織結構

        java中的類加載器大致可以分成兩類,一類是系統提供的,另一類則是由java應用開發人員編寫的。系統提供的類加載主要有下面三個:

       引導類加載器(bootstrap class loader):它用來加載java的核心庫,是用原生代碼來實現的,並不繼承自java.lang.ClassLoader.

       擴展類加載器(extension class loader):它用來加載java的擴展庫。java虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載java類。

       系統類加載器(system class loader):它根據java應用的類路徑(CLASSPATH)來加載java類。一般來說,java應用的類都是由它來完成加載的。可以通過ClassLoader.getSystemClassLoader()來獲取它。除了系統提供的類加載器意外,開發人員通過繼承java.lang.ClassLoader類的方式實現自己的類加載器,以滿足一些特殊需求。

       除了引導類加載器之外,所有的類加載器都有一個加載器。通過表1中給出的getParent()方法可以得到。對於系統提供的類加載器來說,系統類加載器的父類加載器是擴展類加載器,而擴展類加載器的父類加載器是引導類加載器;對於開發人員編寫的類加載器來說,其父類是系統類加載器。

       圖1類加載器樹狀組織結構示意圖

   

  5、類加載器的代理模式

        類加載器在嘗試自己去查找某個類的字節代碼並定義它時,會先代理給其父類加載器,由父類加載器先去嘗試加載這個類,依此類推。在介紹代理模式背後的動機之前,首先需要說明一下java虛擬機是如何判斷兩個java類是相同的。java虛擬機不僅要看類的全名是否相同,還要看加載此類的類加載是否一樣。只有兩者都相同的情況,才認爲兩個類是相同的。即便是同樣的字節代碼,別不同的類加載器加載之後所得到的類,也是不同的。比如一個java類come.example.Sample,編譯之後生成了字節代碼文件Sample.class。兩個不同類加載器ClassLoaderA和ClassLoaderB分別讀取了這個Sample.class文件,並定義了兩個java.lang.Class類的實例來表示這個類。這兩個實例是不同的。對java虛擬機來說,它們是不同的類。試圖對這兩個類的對象進行相互賦值,會拋出運行時異常ClassCastException.

  6、加載類的過程

       在前面介紹類加載器的代理模式的時候,提到過類加載器會首先代理給其它類加載來加載某個類。這就意味着真正完成類的加載工作的類加載器和啓動這個加載過程的類加載器,有可能不是同一個。真正完成類加載工作是通過調用defineClass來實現的;而啓動類的加載過程是通過調用loadClass來實現的。前者稱爲一個類的定義加載器(defining loader),後者稱爲初始加載器(initiating loader)。在java虛擬機判斷兩個類是否相同的時候,使用的是類的定義加載器。也就是說,哪個類加載器啓動類的加載過程並不重要,重要的是最終定義這個類的加載器。兩種類加載器的關聯之處在於:一個類的定義加載器是它引用的其它類的初始加載器。如類com.example.Outer引用了類com.example.Inner,則由類com.example.Outer的定義加載器負責啓動類com.example.Inner的加載過程。

  7、線程的上下文類加載器

線程上下文類加載器(context class loader)是從 JDK 1.2 開始引入的。類 java.lang.Thread中的方法 getContextClassLoader()setContextClassLoader(ClassLoader cl)用來獲取和設置線程的上下文類加載器。如果沒有通過 setContextClassLoader(ClassLoader cl)方法進行設置的話,線程將繼承其父線程的上下文類加載器。Java 應用運行的初始線程的上下文類加載器是系統類加載器。在線程中運行的代碼可以通過此類加載器來加載類和資源。

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