所謂裝載就是尋找一個(gè)類或是一個(gè)接口的二進(jìn)制形式并用該二進(jìn)制形式來(lái)構(gòu)造代表這個(gè)類或是這個(gè)接口的class對(duì)象的過(guò)程,其中類或接口的名稱是給定了的。當(dāng)然名稱也可以通過(guò)計(jì)算得到,但是更常見的是通過(guò)搜索源代碼經(jīng)過(guò)編譯器編譯后所得到的二進(jìn)制形式來(lái)構(gòu)造。
在Java中,類裝載器把一個(gè)類裝入Java虛擬機(jī)中,要經(jīng)過(guò)三個(gè)步驟來(lái)完成:裝載、鏈接和初始化,其中鏈接又可以分成校驗(yàn)、準(zhǔn)備和解析三步,除了解析外,其它步驟是嚴(yán)格按照順序完成的,各個(gè)步驟的主要工作如下:
裝載:查找和導(dǎo)入類或接口的二進(jìn)制數(shù)據(jù);
鏈接:執(zhí)行下面的校驗(yàn)、準(zhǔn)備和解析步驟,其中解析步驟是可以選擇的;
校驗(yàn):檢查導(dǎo)入類或接口的二進(jìn)制數(shù)據(jù)的正確性;
準(zhǔn)備:給類的靜態(tài)變量分配并初始化存儲(chǔ)空間;
解析:將符號(hào)引用轉(zhuǎn)成直接引用;
初始化:激活類的靜態(tài)變量的初始化Java代碼和靜態(tài)Java代碼塊。
2.2裝載的實(shí)現(xiàn)
JVM中類的裝載是由ClassLoader和它的子類來(lái)實(shí)現(xiàn)的,Java ClassLoader是一個(gè)重要的Java運(yùn)行時(shí)系統(tǒng)組件。它負(fù)責(zé)在運(yùn)行時(shí)查找和裝入類文件的類。
在Java中,ClassLoader是一個(gè)抽象類,它在包java.lang中,可以這樣說(shuō),只要了解了在ClassLoader中的一些重要的方法,再結(jié)合上面所介紹的JVM中類裝載的具體的過(guò)程,對(duì)動(dòng)態(tài)裝載類這項(xiàng)技術(shù)就有了一個(gè)比較大概的掌握,這些重要的方法包括以下幾個(gè):
①loadCass方法loadClass(String name ,boolean resolve)其中name參數(shù)指定了JVM需要的類的名稱,該名稱以包表示法表示,如Java.lang.Object;resolve參數(shù)告訴方法是否需要解析類,在初始化類之前,應(yīng)考慮類解析,并不是所有的類都需要解析,如果JVM只需要知道該類是否存在或找出該類的超類,那么就不需要解析。這個(gè)方法是ClassLoader的入口點(diǎn)。
②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)有效性等等。
③findSystemClass方法findSystemClass方法從本地文件系統(tǒng)裝入文件。它在本地文件系統(tǒng)中尋找類文件,如果存在,就使用defineClass將字節(jié)數(shù)組轉(zhuǎn)換成Class對(duì)象,以將該文件轉(zhuǎn)換成類。當(dāng)運(yùn)行Java應(yīng)用 程序 時(shí),這是JVM正常裝入類的缺省機(jī)制。
④resolveClass方法resolveClass(Class c)方法解析裝入的類,如果該類已經(jīng)被解析過(guò)那么將不做處理。當(dāng)調(diào)用loadClass方法時(shí),通過(guò)它的resolve參數(shù)決定是否要進(jìn)行解析。
⑤findLoadedClass方法當(dāng)調(diào)用loadClass方法裝入類時(shí),調(diào)用findLoadedClass方法來(lái)查看ClassLoader是否已裝入這個(gè)類,如果已裝入,那么返回Class對(duì)象,否則返回NULL。如果強(qiáng)行裝載已存在的類,將會(huì)拋出鏈接錯(cuò)誤。
在Java中,類裝載器把一個(gè)類裝入Java虛擬機(jī)中,要經(jīng)過(guò)三個(gè)步驟來(lái)完成:裝載、鏈接和初始化,其中鏈接又可以分成校驗(yàn)、準(zhǔn)備和解析三步,除了解析外,其它步驟是嚴(yán)格按照順序完成的,各個(gè)步驟的主要工作如下:
裝載:查找和導(dǎo)入類或接口的二進(jìn)制數(shù)據(jù);
鏈接:執(zhí)行下面的校驗(yàn)、準(zhǔn)備和解析步驟,其中解析步驟是可以選擇的;
校驗(yàn):檢查導(dǎo)入類或接口的二進(jìn)制數(shù)據(jù)的正確性;
準(zhǔn)備:給類的靜態(tài)變量分配并初始化存儲(chǔ)空間;
解析:將符號(hào)引用轉(zhuǎn)成直接引用;
初始化:激活類的靜態(tài)變量的初始化Java代碼和靜態(tài)Java代碼塊。
2.2裝載的實(shí)現(xiàn)
JVM中類的裝載是由ClassLoader和它的子類來(lái)實(shí)現(xiàn)的,Java ClassLoader是一個(gè)重要的Java運(yùn)行時(shí)系統(tǒng)組件。它負(fù)責(zé)在運(yùn)行時(shí)查找和裝入類文件的類。
在Java中,ClassLoader是一個(gè)抽象類,它在包java.lang中,可以這樣說(shuō),只要了解了在ClassLoader中的一些重要的方法,再結(jié)合上面所介紹的JVM中類裝載的具體的過(guò)程,對(duì)動(dòng)態(tài)裝載類這項(xiàng)技術(shù)就有了一個(gè)比較大概的掌握,這些重要的方法包括以下幾個(gè):
①loadCass方法loadClass(String name ,boolean resolve)其中name參數(shù)指定了JVM需要的類的名稱,該名稱以包表示法表示,如Java.lang.Object;resolve參數(shù)告訴方法是否需要解析類,在初始化類之前,應(yīng)考慮類解析,并不是所有的類都需要解析,如果JVM只需要知道該類是否存在或找出該類的超類,那么就不需要解析。這個(gè)方法是ClassLoader的入口點(diǎn)。
②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)有效性等等。
③findSystemClass方法findSystemClass方法從本地文件系統(tǒng)裝入文件。它在本地文件系統(tǒng)中尋找類文件,如果存在,就使用defineClass將字節(jié)數(shù)組轉(zhuǎn)換成Class對(duì)象,以將該文件轉(zhuǎn)換成類。當(dāng)運(yùn)行Java應(yīng)用 程序 時(shí),這是JVM正常裝入類的缺省機(jī)制。
④resolveClass方法resolveClass(Class c)方法解析裝入的類,如果該類已經(jīng)被解析過(guò)那么將不做處理。當(dāng)調(diào)用loadClass方法時(shí),通過(guò)它的resolve參數(shù)決定是否要進(jìn)行解析。
⑤findLoadedClass方法當(dāng)調(diào)用loadClass方法裝入類時(shí),調(diào)用findLoadedClass方法來(lái)查看ClassLoader是否已裝入這個(gè)類,如果已裝入,那么返回Class對(duì)象,否則返回NULL。如果強(qiáng)行裝載已存在的類,將會(huì)拋出鏈接錯(cuò)誤。
2.3裝載的應(yīng)用
一般來(lái)說(shuō),我們使用虛擬機(jī)的類裝載時(shí)需要繼承抽象類 java .lang.ClassLoader,其中必須實(shí)現(xiàn)的方法是loadClass(),對(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的類裝載 模型 是一種代理(delegation)模型。當(dāng)JVM要求類裝載器CL(ClassLoader)裝載一個(gè)類時(shí),CL首先將這個(gè)類裝載請(qǐng)求轉(zhuǎn)發(fā)給他的父裝載器。只有當(dāng)父裝載器沒(méi)有裝載并無(wú)法裝載這個(gè)類時(shí),CL才獲得裝載這個(gè)類的機(jī)會(huì)。這樣,所有類裝載器的代理關(guān)系構(gòu)成了一種樹狀的關(guān)系。樹的根是類的根裝載器(bootstrap ClassLoader) ,在JVM中它以"null"表示。除根裝載器以外的類裝載器有且僅有一個(gè)父裝載器。在創(chuàng)建一個(gè)裝載器時(shí),如果沒(méi)有顯式地給出父裝載器,那么JVM將默認(rèn)系統(tǒng)裝載器為其父裝載器。Java的基本類裝載器代理結(jié)構(gòu)如圖2所示:
下面針對(duì)各種類裝載器分別進(jìn)行詳細(xì)的說(shuō)明。
根(Bootstrap)裝載器:該裝載器沒(méi)有父裝載器,它是JVM實(shí)現(xiàn)的一部分,從sun.boot.class.path裝載運(yùn)行時(shí)庫(kù)的核心代碼。
擴(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中的類都隱式含有裝載它的類裝載器的信息。類方法getClassLoader可以得到裝載這個(gè)類的類裝載器。一個(gè)類裝載器認(rèn)識(shí)的類包括它的父裝載器認(rèn)識(shí)的類和它自己裝載的類,可見類裝載器認(rèn)識(shí)的類是它自己裝載的類的超集。注意我們可以得到類裝載器的有關(guān)的信息,但是已經(jīng)裝載到JVM中的類是不能更改它的類裝載器的。
Java中的類的裝載過(guò)程也就是代理裝載的過(guò)程。比如:Web瀏覽器中的JVM需要裝載一個(gè)小應(yīng)用程序TestApplet。JVM調(diào)用小應(yīng)用程序裝載器ACL(Applet ClassLoader)來(lái)完成裝載。ACL首先請(qǐng)求它的父裝載器,即系統(tǒng)裝載器裝載TestApplet是否裝載了這個(gè)類,由于TestApplet不在系統(tǒng)裝載器的裝載路徑中,所以系統(tǒng)裝載器沒(méi)有找到這個(gè)類,也就沒(méi)有裝載成功。接著ACL自己裝載TestApplet。ACL通過(guò)網(wǎng)絡(luò)成功地找到了TestApplet.class文件并將它導(dǎo)入到了JVM中。在裝載過(guò)程中, JVM發(fā)現(xiàn)TestAppet是從超類java.applet.Applet繼承的。所以JVM再次調(diào)用ACL來(lái)裝載java.applet.Applet類。ACL又再次按上面的順序裝載Applet類,結(jié)果ACL發(fā)現(xiàn)他的父裝載器已經(jīng)裝載了這個(gè)類,所以ACL就直接將這個(gè)已經(jīng)裝載的類返回給了JVM ,完成了Applet類的裝載。接下來(lái),Applet類的超類也一樣處理。最后, TestApplet及所有有關(guān)的類都裝載到了JVM中。
四、結(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è)比較深入的理解。
一般來(lái)說(shuō),我們使用虛擬機(jī)的類裝載時(shí)需要繼承抽象類 java .lang.ClassLoader,其中必須實(shí)現(xiàn)的方法是loadClass(),對(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)置的類庫(kù)裝載器被稱為根裝載器(bootstrap ClassLoader)。根裝載器的特殊之處是它只能夠裝載在
設(shè)計(jì)
時(shí)刻已知的類,因此虛擬機(jī)假定由根裝載器所裝載的類都是
安全
的、可信任的,可以不經(jīng)過(guò)安全認(rèn)證而直接運(yùn)行。當(dāng)應(yīng)用
程序
需要加載并不是設(shè)計(jì)時(shí)就知道的類時(shí),必須使用用戶自定義的裝載器(user-defined ClassLoader)。下面我們舉例說(shuō)明它的應(yīng)用。
三、
Java
虛擬機(jī)的類裝載原理
前面我們已經(jīng)知道,一個(gè)Java應(yīng)用 程序 使用兩種類型的類裝載器:根裝載器(bootstrap)和用戶定義的裝載器(user-defined)。根裝載器是Java虛擬機(jī)實(shí)現(xiàn)的一部分,舉個(gè)例子來(lái)說(shuō),如果一個(gè)Java虛擬機(jī)是在現(xiàn)在已經(jīng)存在并且正在被使用的 操作系統(tǒng) 的頂部用C程序來(lái)實(shí)現(xiàn)的,那么根裝載器將是那些C程序的一部分。根裝載器以某種默認(rèn)的方式將類裝入,包括那些Java API的類。在運(yùn)行期間一個(gè)Java程序能安裝用戶自己定義的類裝載器。根裝載器是虛擬機(jī)固有的一部分,而用戶定義的類裝載器則不是,它是用Java語(yǔ)言寫的,被編譯成class文件之后然后再被裝入到虛擬機(jī),并像其它的任何對(duì)象一樣可以被實(shí)例化。Java類裝載器的體系結(jié)構(gòu)如下所示:
前面我們已經(jīng)知道,一個(gè)Java應(yīng)用 程序 使用兩種類型的類裝載器:根裝載器(bootstrap)和用戶定義的裝載器(user-defined)。根裝載器是Java虛擬機(jī)實(shí)現(xiàn)的一部分,舉個(gè)例子來(lái)說(shuō),如果一個(gè)Java虛擬機(jī)是在現(xiàn)在已經(jīng)存在并且正在被使用的 操作系統(tǒng) 的頂部用C程序來(lái)實(shí)現(xiàn)的,那么根裝載器將是那些C程序的一部分。根裝載器以某種默認(rèn)的方式將類裝入,包括那些Java API的類。在運(yùn)行期間一個(gè)Java程序能安裝用戶自己定義的類裝載器。根裝載器是虛擬機(jī)固有的一部分,而用戶定義的類裝載器則不是,它是用Java語(yǔ)言寫的,被編譯成class文件之后然后再被裝入到虛擬機(jī),并像其它的任何對(duì)象一樣可以被實(shí)例化。Java類裝載器的體系結(jié)構(gòu)如下所示:
圖1 Java的類裝載的體系結(jié)構(gòu) |
Java的類裝載 模型 是一種代理(delegation)模型。當(dāng)JVM要求類裝載器CL(ClassLoader)裝載一個(gè)類時(shí),CL首先將這個(gè)類裝載請(qǐng)求轉(zhuǎn)發(fā)給他的父裝載器。只有當(dāng)父裝載器沒(méi)有裝載并無(wú)法裝載這個(gè)類時(shí),CL才獲得裝載這個(gè)類的機(jī)會(huì)。這樣,所有類裝載器的代理關(guān)系構(gòu)成了一種樹狀的關(guān)系。樹的根是類的根裝載器(bootstrap ClassLoader) ,在JVM中它以"null"表示。除根裝載器以外的類裝載器有且僅有一個(gè)父裝載器。在創(chuàng)建一個(gè)裝載器時(shí),如果沒(méi)有顯式地給出父裝載器,那么JVM將默認(rèn)系統(tǒng)裝載器為其父裝載器。Java的基本類裝載器代理結(jié)構(gòu)如圖2所示:
圖2 Java類裝載的代理結(jié)構(gòu) |
下面針對(duì)各種類裝載器分別進(jìn)行詳細(xì)的說(shuō)明。
根(Bootstrap)裝載器:該裝載器沒(méi)有父裝載器,它是JVM實(shí)現(xiàn)的一部分,從sun.boot.class.path裝載運(yùn)行時(shí)庫(kù)的核心代碼。
擴(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中的類都隱式含有裝載它的類裝載器的信息。類方法getClassLoader可以得到裝載這個(gè)類的類裝載器。一個(gè)類裝載器認(rèn)識(shí)的類包括它的父裝載器認(rèn)識(shí)的類和它自己裝載的類,可見類裝載器認(rèn)識(shí)的類是它自己裝載的類的超集。注意我們可以得到類裝載器的有關(guān)的信息,但是已經(jīng)裝載到JVM中的類是不能更改它的類裝載器的。
Java中的類的裝載過(guò)程也就是代理裝載的過(guò)程。比如:Web瀏覽器中的JVM需要裝載一個(gè)小應(yīng)用程序TestApplet。JVM調(diào)用小應(yīng)用程序裝載器ACL(Applet ClassLoader)來(lái)完成裝載。ACL首先請(qǐng)求它的父裝載器,即系統(tǒng)裝載器裝載TestApplet是否裝載了這個(gè)類,由于TestApplet不在系統(tǒng)裝載器的裝載路徑中,所以系統(tǒng)裝載器沒(méi)有找到這個(gè)類,也就沒(méi)有裝載成功。接著ACL自己裝載TestApplet。ACL通過(guò)網(wǎng)絡(luò)成功地找到了TestApplet.class文件并將它導(dǎo)入到了JVM中。在裝載過(guò)程中, JVM發(fā)現(xiàn)TestAppet是從超類java.applet.Applet繼承的。所以JVM再次調(diào)用ACL來(lái)裝載java.applet.Applet類。ACL又再次按上面的順序裝載Applet類,結(jié)果ACL發(fā)現(xiàn)他的父裝載器已經(jīng)裝載了這個(gè)類,所以ACL就直接將這個(gè)已經(jīng)裝載的類返回給了JVM ,完成了Applet類的裝載。接下來(lái),Applet類的超類也一樣處理。最后, TestApplet及所有有關(guān)的類都裝載到了JVM中。
四、結(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è)比較深入的理解。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
