Java類(lèi)加載機制復習
簡(jiǎn)介
顧名思義,類(lèi)加載器(class loader)用來(lái)加載 Java 類(lèi)到 Java 虛擬機中。一般來(lái)說(shuō),Java 虛擬機使用 Java 類(lèi)的方式如下:Java 源程序(.java 文件)在經(jīng)過(guò) Java 編譯器編譯之后就被轉換成 Java 字節代碼(.class 文件)。類(lèi)加載器負責讀取 Java 字節代碼,并轉換成 java.lang.Class
類(lèi)的一個(gè)實(shí)例。每個(gè)這樣的實(shí)例用來(lái)表示一個(gè) Java 類(lèi)。通過(guò)此實(shí)例的 newInstance()
方法就可以創(chuàng )建出該類(lèi)的一個(gè)對象。實(shí)際的情況可能更加復雜,比如 Java 字節代碼可能是通過(guò)工具動(dòng)態(tài)生成的,也可能是通過(guò)網(wǎng)絡(luò )下載的。
類(lèi)加載器的樹(shù)狀組織結構
Java 中的類(lèi)加載器大致可以分成兩類(lèi),一類(lèi)是系統提供的,另外一類(lèi)則是由 Java 應用開(kāi)發(fā)人員編寫(xiě)的。系統提供的類(lèi)加載器主要有下面三個(gè):
- 引導類(lèi)加載器(bootstrap class loader):它用來(lái)加載 Java 的核心庫,是用原生代碼來(lái)實(shí)現的,并不繼承自 java.lang.ClassLoader。
- 擴展類(lèi)加載器(extensions class loader):它用來(lái)加載 Java 的擴展庫。Java 虛擬機的實(shí)現會(huì )提供一個(gè)擴展庫目錄。該類(lèi)加載器在此目錄里面查找并加載 Java 類(lèi)。
- 系統類(lèi)加載器(system class loader):它根據 Java 應用的類(lèi)路徑(CLASSPATH)來(lái)加載 Java 類(lèi)。一般來(lái)說(shuō),Java 應用的類(lèi)都是由它來(lái)完成加載的??梢酝ㄟ^(guò)
ClassLoader.getSystemClassLoader()
來(lái)獲取它。
除了系統提供的類(lèi)加載器以外,開(kāi)發(fā)人員可以通過(guò)繼承 java.lang.ClassLoader
類(lèi)的方式實(shí)現自己的類(lèi)加載器,以滿(mǎn)足一些特殊的需求。
加載器樹(shù)狀組織結構示意圖:
雙親委派模式
雙親委派模式工作原理
雙親委派模式要求除了頂層的啟動(dòng)類(lèi)加載器外,其余的類(lèi)加載器都應當有自己的父類(lèi)加載器,請注意雙親委派模式中的父子關(guān)系并非通常所說(shuō)的類(lèi)繼承關(guān)系,而是采用組合關(guān)系來(lái)復用父類(lèi)加載器的相關(guān)代碼,類(lèi)加載器間的關(guān)系如下:
其工作原理的是,如果一個(gè)類(lèi)加載器收到了類(lèi)加載請求,它并不會(huì )自己先去加載,而是把這個(gè)請求委托給父類(lèi)的加載器去執行,如果父類(lèi)加載器還存在其父類(lèi)加載器,則進(jìn)一步向上委托,依次遞歸,請求最終將到達頂層的啟動(dòng)類(lèi)加載器,如果父類(lèi)加載器可以完成類(lèi)加載任務(wù),就成功返回,倘若父類(lèi)加載器無(wú)法完成此加載任務(wù),子加載器才會(huì )嘗試自己去加載,這就是雙親委派模式。
雙親委派模式優(yōu)勢
采用雙親委派模式的是好處是 Java 類(lèi)隨著(zhù)它的類(lèi)加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系,通過(guò)這種層級關(guān)可以避免類(lèi)的重復加載,當父親已經(jīng)加載了該類(lèi)時(shí),就沒(méi)有必要子 ClassLoader 再加載一次。其次是考慮到安全因素,Java 核心 API 中定義類(lèi)型不會(huì )被隨意替換,假設通過(guò)網(wǎng)絡(luò )傳遞一個(gè)名為 java.lang.Integer
的類(lèi),通過(guò)雙親委托模式傳遞到啟動(dòng)類(lèi)加載器,而啟動(dòng)類(lèi)加載器在核心 Java API 發(fā)現這個(gè)名字的類(lèi),發(fā)現該類(lèi)已被加載,并不會(huì )重新加載網(wǎng)絡(luò )傳遞的過(guò)來(lái)的 java.lang.Integer,而直接返回已加載過(guò)的 Integer.class,這樣便可以防止核心 API 庫被隨意篡改??赡苣銜?huì )想,如果我們在 classpath 路徑下自定義一個(gè)名為 java.lang.SingleInterge 類(lèi)(該類(lèi)是胡編的)呢?該類(lèi)并不存在 java.lang 中,經(jīng)過(guò)雙親委托模式,傳遞到啟動(dòng)類(lèi)加載器中,由于父類(lèi)加載器路徑下并沒(méi)有該類(lèi),所以不會(huì )加載,將反向委托給子類(lèi)加載器加載,最終會(huì )通過(guò)系統類(lèi)加載器加載該類(lèi)。
線(xiàn)程上下文類(lèi)加載器
線(xiàn)程上下文類(lèi)加載器(context class loader)是從 JDK 1.2 開(kāi)始引入的。類(lèi) java.lang.Thread
中的方法 getContextClassLoader()
和 setContextClassLoader(ClassLoader cl)
用來(lái)獲取和設置線(xiàn)程的上下文類(lèi)加載器。如果沒(méi)有通過(guò) setContextClassLoader(ClassLoader cl)
方法進(jìn)行設置的話(huà),線(xiàn)程將繼承其父線(xiàn)程的上下文類(lèi)加載器。Java 應用運行的初始線(xiàn)程的上下文類(lèi)加載器是系統類(lèi)加載器。在線(xiàn)程中運行的代碼可以通過(guò)此類(lèi)加載器來(lái)加載類(lèi)和資源。
在 Java 應用中存在著(zhù)很多服務(wù)提供者接口(Service Provider Interface,SPI),這些接口允許第三方為它們提供實(shí)現,如常見(jiàn)的 SPI 有 JDBC、JNDI 等,這些 SPI 的接口屬于 Java 核心庫,一般存在 rt.jar 包中,由 Bootstrap 類(lèi)加載器加載,而 SPI 的第三方實(shí)現代碼則是作為 Java 應用所依賴(lài)的 jar 包被存放在 classpath 路徑下,由于 SPI 接口中的代碼經(jīng)常需要加載具體的第三方實(shí)現類(lèi)并調用其相關(guān)方法,但 SPI 的核心接口類(lèi)是由引導類(lèi)加載器來(lái)加載的,而 Bootstrap 類(lèi)加載器無(wú)法直接加載 SPI 的實(shí)現類(lèi),同時(shí)由于雙親委派模式的存在,Bootstrap 類(lèi)加載器也無(wú)法反向委托 AppClassLoader 加載器 SPI 的實(shí)現類(lèi)。在這種情況下,我們就需要一種特殊的類(lèi)加載器來(lái)加載第三方的類(lèi)庫,而線(xiàn)程上下文類(lèi)加載器就是很好的選擇。
參考文檔
