ClassLoader
是
Java
虛擬機(jī)
(JVM)
的類裝載子系統(tǒng),它負(fù)責(zé)將
Java
字節(jié)碼裝載到
JVM
中,
并使其成為
JVM
一部分。
JVM
的類動(dòng)態(tài)裝載技術(shù)能夠在運(yùn)行時(shí)刻動(dòng)態(tài)地加載或者替換系統(tǒng)的某些功能模塊
,
而不影響系統(tǒng)其他功能模塊的正常運(yùn)行。本文將分析
JVM
中的類裝載系統(tǒng),探討
JVM
中類裝載的原理、實(shí)現(xiàn)以及應(yīng)用。
類裝載就是尋找一個(gè)類或是一個(gè)接口的字節(jié)碼文件并通過解析該字節(jié)碼來構(gòu)造代表這個(gè)類或是這個(gè)接口的
class
對(duì)象的過程。在
Java
中,類裝載器把一個(gè)類裝入
Java
虛擬機(jī)中,要經(jīng)過三個(gè)步驟來完成:裝載、鏈接和初始化,其中鏈接又可以分成校驗(yàn)、準(zhǔn)備和解析三步,除了解析外,其它步驟是嚴(yán)格按照順序完成的,各個(gè)步驟的主要工作如下:
1.???????
裝載:查找和導(dǎo)入類或接口的字節(jié)碼;
2.???????
鏈接:執(zhí)行下面的校驗(yàn)、準(zhǔn)備和解析步驟,其中解析步驟是可以選擇的;
l???????
校驗(yàn):檢查導(dǎo)入類或接口的二進(jìn)制數(shù)據(jù)的正確性;
l???????
準(zhǔn)備:給類的靜態(tài)變量分配并初始化存儲(chǔ)空間;
l???????
解析:將符號(hào)引用轉(zhuǎn)成直接引用;
3.???????
初始化:激活類的靜態(tài)變量的初始化
Java
代碼和靜態(tài)
Java
代碼塊。
至于在類裝載和虛擬機(jī)啟動(dòng)的過程中的具體細(xì)節(jié)和可能會(huì)拋出的錯(cuò)誤,請(qǐng)參看《
Java
虛擬機(jī)規(guī)范》以及《深入
Java
虛擬機(jī)》。
由于本文的討論重點(diǎn)不在此就不再多敘述。
JVM
中類的裝載是由
ClassLoader
和它的子類來實(shí)現(xiàn)的。
Java ClassLoader
是一個(gè)重要的
Java
運(yùn)行時(shí)系統(tǒng)組件,它負(fù)責(zé)在運(yùn)行時(shí)查找和裝入
Java
字節(jié)碼。
在
Java
中,
ClassLoader
是一個(gè)抽象類,它在包
java.lang
中。可以這樣說,只要了解了
ClassLoader
中的一些重要的方法,再結(jié)合上面所介紹的
JVM
中類裝載的具體的過程,對(duì)動(dòng)態(tài)裝載類這項(xiàng)技術(shù)就有了一個(gè)比較大概的掌握,這些重要的方法包括以下幾個(gè):
1.???????
loadCass
方法:
loadClass(String name ,boolean resolve)
其中
name
參數(shù)指定了
JVM
需要的類的名稱
,
該名稱以類的全限定名表示,如
Java.lang.Object
;
resolve
參數(shù)告訴方法是否需要解析類,在初始化類之前,應(yīng)考慮類解析,并不是所有的類都需要解析,如果
JVM
只需要知道該類是否存在或找出該類的超類,那么就不需要解析。這個(gè)方法是
ClassLoader
的入口點(diǎn)。
2.???????
defineClass
方法
?
這個(gè)方法接受類文件的字節(jié)數(shù)組并把它轉(zhuǎn)換成
Class
對(duì)象。字節(jié)數(shù)組可以是從本地文件系統(tǒng)或網(wǎng)絡(luò)裝入的數(shù)據(jù)。它把字節(jié)碼分析成運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)、校驗(yàn)有效性等等。
3.???????
findSystemClass
方法
? findSystemClass
方法從本地文件系統(tǒng)裝入
Java
字節(jié)碼。它在本地文件系統(tǒng)中尋找類文件,如果存在,就使用
defineClass
將字節(jié)數(shù)組轉(zhuǎn)換成
Class
對(duì)象。當(dāng)運(yùn)行
Java
應(yīng)用程序時(shí)
,
這是
JVM
正常裝入類的缺省機(jī)制。
4.???????
resolveClass
方法
resolveClass(Class c)
方法解析裝入的類,如果該類已經(jīng)被解析過那么將不做處理。當(dāng)調(diào)用
loadClass
方法時(shí)
,
通過它的
resolve
參數(shù)決定是否要進(jìn)行解析。
5.???????
findLoadedClass
方法
?
當(dāng)調(diào)用
loadClass
方法裝入類時(shí)
,
調(diào)用
findLoadedClass
方法來查看
ClassLoader
是否已裝入這個(gè)類
,
如果已裝入
,
那么返回
Class
對(duì)象
,
否則返回
NULL
。如果強(qiáng)行裝載已存在的類
,
將會(huì)拋出鏈接錯(cuò)誤。
一般來說,我們實(shí)現(xiàn)自定義的
ClassLoader
需要繼承抽象類
java.lang.ClassLoader
,其中必須實(shí)現(xiàn)的方法是
loadClass(String name)
,對(duì)于這個(gè)方法需要實(shí)現(xiàn)如下操作:
(1)
確認(rèn)類的名稱;
(2)
檢查請(qǐng)求要裝載的類是否已經(jīng)被裝載;
(3)
檢查請(qǐng)求加載的類是否是系統(tǒng)類;
(4)
嘗試從類裝載器的存儲(chǔ)區(qū)獲取所請(qǐng)求的類;
(5)
在虛擬機(jī)中定義所請(qǐng)求的類;
(6)
解析所請(qǐng)求的類;
(7)
返回所請(qǐng)求的類。
所有的
Java
虛擬機(jī)都包括一個(gè)內(nèi)置的類裝載器,這個(gè)內(nèi)置的類庫裝載器被稱為根裝載器
(bootstrap ClassLoader)
。根裝載器的特殊之處是它只能夠裝載基本的
Java
類,如
rt.jar
中的
class
。當(dāng)應(yīng)用程序可以使用用戶自定義的
ClassLoader
來加載特定
ClassPath
下的類。下面的例子是
JDK5.0
的
URLClassLoader
的實(shí)現(xiàn)。
public class URLClassLoader extends SecureClassLoader {
……
protected Class<?> findClass(final String name)
??????
?throws ClassNotFoundException
??? {
?????? try {
??????
??? return (Class)
????????????? AccessController.doPrivileged(new PrivilegedExceptionAction() {
?????????????
??? public Object run() throws ClassNotFoundException {
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?//
由類的全限定名得到物理路徑
???????????????????? String path = name.replace('.', '/').concat(".class");
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?//
從
URLs ClassPath
中取得相應(yīng)的字節(jié)碼
???????????????????? Resource res = ucp.getResource(path, false);
???????????????????? if (res != null) {
????????????????????
??? try {
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?//
由
Java
字節(jié)碼創(chuàng)建一個(gè)
Class
對(duì)象
??????????????????????????? return defineClass(name, res);
????????????????????
??? } catch (IOException e) {
??????????????????????????? throw new ClassNotFoundException(name, e);
????????????????????
??? }
???????????????????? } else {
????????????????????
??? throw new ClassNotFoundException(name);
???????????????????? }
?????????????
??? }
????????????? }, acc);
?????? } catch (java.security.PrivilegedActionException pae) {
??????
??? throw (ClassNotFoundException) pae.getException();
?????? }
??? }
……
}
|
?
2.3
?????????
Java
虛擬機(jī)的類裝載原理
前面我們已經(jīng)知道,一個(gè)
Java
應(yīng)用程序使用兩種類型的類裝載器:根裝載器
(bootstrap)
和用戶定義的裝載器
(user-defined)
。根裝載器是
Java
虛擬機(jī)實(shí)現(xiàn)的一部分。根裝載器以某種默認(rèn)的方式將類裝入,包括
Java API
的類。在運(yùn)行期間,一個(gè)
Java
程序能使用用戶自己定義的類裝載器。根裝載器是虛擬機(jī)固有的一部分,而用戶定義的類裝載器則不是,它是用
Java
語言寫的,被編譯成
class
文件之后然后再被裝入到虛擬機(jī),并像其它的任何對(duì)象一樣可以被實(shí)例化。
Java
類裝載器的體系結(jié)構(gòu)如下所示:
Java
的類裝載的體系結(jié)構(gòu)
Java
的類裝載模型是一種代理
(delegation)
模型。當(dāng)
JVM
要求類裝載器
CL(ClassLoader)
裝載一個(gè)類時(shí)
,CL
首先將這個(gè)類裝載請(qǐng)求轉(zhuǎn)發(fā)給他的父裝載器。只有當(dāng)父裝載器沒有裝載并無法裝載這個(gè)類時(shí)
,CL
才獲得裝載這個(gè)類的機(jī)會(huì)。這樣
,
所有類裝載器的代理關(guān)系構(gòu)成了一種樹狀的關(guān)系。樹的根是類的根裝載器
(bootstrap ClassLoader) ,
在
JVM
中它以
"null"
表示。除根裝載器以外的類裝載器有且僅有一個(gè)父裝載器。在創(chuàng)建一個(gè)裝載器時(shí)
,
如果沒有顯式地給出父裝載器
,
那么
JVM
將默認(rèn)系統(tǒng)裝載器為其父裝載器。
Java
的基本類裝載器代理結(jié)構(gòu)如圖
2
所示:
Java
類裝載的代理結(jié)構(gòu)
下面針對(duì)各種類裝載器分別進(jìn)行詳細(xì)的說明。
根
(Bootstrap)
裝載器:該裝載器沒有父裝載器,它是
JVM
實(shí)現(xiàn)的一部分,從
sun.boot.class.path
裝載運(yùn)行時(shí)庫的核心代碼。
?
擴(kuò)展
(Extension)
裝載器:繼承的父裝載器為根裝載器,不像根裝載器可能與運(yùn)行時(shí)的操作系統(tǒng)有關(guān),這個(gè)類裝載器是用純
Java
代碼實(shí)現(xiàn)的,它從
java.ext.dirs (
擴(kuò)展目錄
)
中裝載代碼。
系統(tǒng)
(System or Application)
裝載器:裝載器為擴(kuò)展裝載器,我們都知道在安裝
JDK
的時(shí)候要設(shè)置環(huán)境變量
(CLASSPATH )
,這個(gè)類裝載器就是從
java.class.path(CLASSPATH
環(huán)境變量
)
中裝載代碼的,它也是用純
Java
代碼實(shí)現(xiàn)的,同時(shí)還是用戶自定義類裝載器的缺省父裝載器。
小應(yīng)用程序
(Applet)
裝載器:父裝載器為系統(tǒng)裝載器,它從用戶指定的網(wǎng)絡(luò)上的特定目錄裝載小應(yīng)用程序代碼。
在設(shè)計(jì)一個(gè)類裝載器的時(shí)候,應(yīng)該滿足以下兩個(gè)條件:
對(duì)于相同的類名,類裝載器所返回的對(duì)象應(yīng)該是同一個(gè)類對(duì)象
如果類裝載器
CL1
將裝載類
C
的請(qǐng)求轉(zhuǎn)給類裝載器
CL2
,那么對(duì)于以下的類或接口,
CL1
和
CL2
應(yīng)該返回同一個(gè)類對(duì)象:
a) ?S
為
C
的直接超類;
b) ?S
為
C
的直接超接口;
c) ?S
為
C
的成員變量的類型;
d) ?S
為
C
的成員方法或構(gòu)建器的參數(shù)類型;
e) ?S
為
C
的成員方法的返回類型。
每個(gè)已經(jīng)裝載到
JVM
中的類對(duì)象都含有裝載它的類裝載器的信息。
Class
類的方法
getClassLoader
可以得到裝載這個(gè)類的類裝載器。一個(gè)類裝載器認(rèn)識(shí)的類包括它的父裝載器認(rèn)識(shí)的類和它自己裝載的類,由此可見類裝載器認(rèn)識(shí)的類是它自己裝載的類的超集。注意,我們可以得到類裝載器的有關(guān)的信息,但是已經(jīng)裝載到
JVM
中的類是不能更改它的類裝載器的。
Java
中的類的裝載過程也就是代理裝載的過程。比如:
Web
瀏覽器中的
JVM
需要裝載一個(gè)小應(yīng)用程序
TestApplet
。
JVM
調(diào)用小應(yīng)用程序裝載器
ACL(Applet ClassLoader)
來完成裝載。
ACL
首先請(qǐng)求它的父裝載器
,
即系統(tǒng)裝載器裝載
TestApplet
是否裝載了這個(gè)類,由于
TestApplet
不在系統(tǒng)裝載器的裝載路徑中
,
所以系統(tǒng)裝載器沒有找到這個(gè)類
,
也就沒有裝載成功。接著
ACL
自己裝載
TestApplet
。
ACL
通過網(wǎng)絡(luò)成功地找到了
TestApplet.class
文件并將它導(dǎo)入到了
JVM
中。在裝載過程中
, JVM
發(fā)現(xiàn)
TestAppet
是從超類
java.applet.Applet
繼承的。所以
JVM
再次調(diào)用
ACL
來裝載
java.applet.Applet
類。
ACL
又再次按上面的順序裝載
Applet
類
,
結(jié)果
ACL
發(fā)現(xiàn)他的父裝載器已經(jīng)裝載了這個(gè)類
,
所以
ACL
就直接將這個(gè)已經(jīng)裝載的類返回給了
JVM ,
完成了
Applet
類的裝載。接下來
,Applet
類的超類也一樣處理。最后
, TestApplet
及所有有關(guān)的類都裝載到了
JVM
中。
2.4
?????????
總結(jié)
??? 類的動(dòng)態(tài)裝載機(jī)制是JVM的一項(xiàng)核心技術(shù), 也是容易被忽視而引起很多誤解的地方。本文介紹了JVM中類裝載的原理、實(shí)現(xiàn)以及應(yīng)用,分析了ClassLoader的結(jié)構(gòu)、用途以及如何利用自定義的ClassLoader裝載并執(zhí)行Java類,希望能使大家對(duì)JVM中的類裝載有一個(gè)比較深入的理解。