簡(jiǎn)介
Java類加載器是Java運(yùn)行時(shí)環(huán)境(Java Runtime Environment)的一部分,負(fù)責(zé)動(dòng)態(tài)加載Java類到Java虛擬機(jī)的內(nèi)存空間中近弟。類通常是按需加載缅糟,即第一次使用該類時(shí)才加載。 由于有了類加載器祷愉,Java運(yùn)行時(shí)系統(tǒng)不需要知道文件與文件系統(tǒng)窗宦。每個(gè)Java類必須由某個(gè)類加載器裝入到內(nèi)存。
類裝載器子系統(tǒng)涉及Java虛擬機(jī)的其他幾個(gè)組成部分二鳄,以及幾個(gè)來(lái)自java.lang
庫(kù)的類赴涵。比如,用戶自定義的類裝載器只是普通的Java對(duì)象订讼,它的類必須派生自java.lang.ClassLoader
髓窜。ClassLoader
中定義的方法為程序提供了訪問(wèn)類裝載器機(jī)制的接口。此外欺殿,對(duì)于每個(gè)被裝載的類型寄纵,Java虛擬機(jī)都會(huì)為他創(chuàng)建一個(gè)java.lang.Class
類的實(shí)例來(lái)代表該類型。和所有其他對(duì)象一樣脖苏,用戶自定義的類裝載器以及Class
類的實(shí)例都放在內(nèi)存中的堆區(qū)程拭,而裝載的類型信息都位于方法區(qū)。
類裝載器子系統(tǒng)除了要定位和導(dǎo)入二進(jìn)制class文件外棍潘,還必須負(fù)責(zé)驗(yàn)證被導(dǎo)入類的正確性恃鞋,為變量分配初始化內(nèi)存,以及幫助解析符號(hào)引用亦歉。這些動(dòng)作必須嚴(yán)格按一下順序完成:
- 裝載--查找并裝載類型的二進(jìn)制數(shù)據(jù)恤浪。
- 鏈接--執(zhí)行驗(yàn)證、準(zhǔn)備以及解析(可選)
- 驗(yàn)證 確保被導(dǎo)入類型的正確性
- 準(zhǔn)備 為類變量分配內(nèi)存肴楷,并將其初始化為默認(rèn)值水由。
- 解析 把類型中的符號(hào)引用轉(zhuǎn)換為直接引用。
- 初始化--把類變量初始化為正確的初始值赛蔫。
分類
在Java虛擬機(jī)中存在多個(gè)類裝載器绷杜,Java應(yīng)用程序可以使用兩種類裝載器:
啟動(dòng)(bootstrap)類裝載器
:此裝載器是Java虛擬機(jī)實(shí)現(xiàn)的一部分。由原生代碼(如C語(yǔ)言)編寫濒募,不繼承自java.lang.ClassLoader
。負(fù)責(zé)加載核心Java庫(kù)圾结,存儲(chǔ)在<JAVA_HOME>/jre/lib
目錄中瑰剃。(如果Java虛擬機(jī)在已有操作系統(tǒng)中實(shí)現(xiàn)為C程序,那么啟動(dòng)類加載器就是此C程序的一部分) 啟動(dòng)類裝載器通常使用某種默認(rèn)的方式從本地磁盤中加載類筝野,包括Java API晌姚。用戶自定義類裝載器
:(包含但不止粤剧,擴(kuò)展類加載器以及系統(tǒng)類加載器) ,繼承自Java中的java.lang.ClassLoader
類挥唠,Java應(yīng)用程序能在運(yùn)行時(shí)安裝用戶自定義類裝載器抵恋,這種累裝載器使用自定義的方式來(lái)裝載類。用戶定義的類裝載器能用Java編寫宝磨,能夠被編譯為Class文件弧关,能被虛擬機(jī)裝載,還能像其他對(duì)象一樣實(shí)例化唤锉。它們實(shí)際上只是運(yùn)行中的Java程序可執(zhí)行代碼的一部分世囊。一般JVM都會(huì)提供一些基本實(shí)現(xiàn)。應(yīng)用程序的開(kāi)發(fā)人員也可以根據(jù)需要編寫自己的類加載器窿祥。JVM中最常使用的是系統(tǒng)類加載器(system)株憾,它用來(lái)啟動(dòng)Java應(yīng)用程序的加載。 通過(guò)java.lang.ClassLoader.getSystemClassLoader()
可以獲取到該類加載器對(duì)象晒衩。該類由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)嗤瞎。
全盤負(fù)責(zé)雙親委托機(jī)制
全盤負(fù)責(zé)
是指當(dāng)一個(gè)ClassLoader裝載一個(gè)類的時(shí),除非顯式地使用另一個(gè)ClassLoader听系,該類所依賴及引用的類也由這個(gè)ClassLoader載入贝奇;“雙親委托機(jī)制”是指先委托父裝載器尋找目標(biāo)類,只有在找不到的情況下才從自己的類路徑中查找并裝載目標(biāo)類跛锌。這一點(diǎn)是從安全角度考慮的弃秆,試想如果有人編寫了一個(gè)惡意的基礎(chǔ)類(如java.lang.String)并裝載到JVM中將會(huì)引起多么可怕的后果。但是由于有了“全盤負(fù)責(zé)委托機(jī)制”髓帽,java.lang.String永遠(yuǎn)是由根裝載器來(lái)裝載的菠赚,這樣就避免了上述事件的發(fā)生。
類加載器需要完成的最終功能是定義一個(gè)Java類郑藏,即把Java字節(jié)代碼轉(zhuǎn)換成JVM中的java.lang.Class
類的對(duì)象衡查。但是類加載的過(guò)程并不是這么簡(jiǎn)單追驴。Java類加載器有兩個(gè)比較重要的特征:
層次組織結(jié)構(gòu)指的是每個(gè)類加載器都有一個(gè)父類加載器假抄,通過(guò)
getParent()
方法可以獲取到。類加載器通過(guò)這種父親-后代的方式組織在一起撮珠,形成樹狀層次結(jié)構(gòu)歌粥。代理模式則指的是一個(gè)類加載器既可以自己完成Java類的定義工作塌忽,也可以代理給其它的類加載器來(lái)完成。由于代理模式的存在失驶,啟動(dòng)一個(gè)類的加載過(guò)程的類加載器和最終定義這個(gè)類的類加載器可能并不是一個(gè)土居。前者稱為初始類加載器,而后者稱為定義類加載器。
兩者的關(guān)聯(lián)在于:在每個(gè)類被裝載的時(shí)候擦耀,Java虛擬機(jī)都會(huì)監(jiān)視這個(gè)類棉圈,看它到底是被啟動(dòng)類裝載器還是被用戶自定義類裝載器裝載。當(dāng)被裝載的類引用了另外一個(gè)類的時(shí)候眷蜓,虛擬機(jī)就會(huì)使用裝載第一個(gè)類的類裝載器裝載被引用的類分瘾。
注意:JVM加載類A,并使用A的ClassLoader去加載B吁系,但B的類加載器并不一定和A的類加載器一致德召,這是因?yàn)橛须p親委托機(jī)制的存在。
一般的類加載器在嘗試自己去加載某個(gè)Java類之前垮抗,會(huì) 首先代理給其父類加載器氏捞。當(dāng)父類加載器找不到的時(shí)候,才會(huì)嘗試自己加載冒版。這個(gè)邏輯是封裝在java.lang.ClassLoader
類的loadClass()
方法中的液茎。一般來(lái)說(shuō),父類優(yōu)先的策略就足夠好了辞嗡。在某些情況下捆等,可能需要采取相反的策略,即先嘗試自己加載续室,找不到的時(shí)候再代理給父類加載器栋烤。這種做法在Java的Web容器中比較常見(jiàn),也是Servlet規(guī)范推薦的做法挺狰。 比如明郭,Apache Tomcat為每個(gè)Web應(yīng)用都提供一個(gè)獨(dú)立的類加載器,使用的就是自己優(yōu)先加載的策略丰泊。IBM WebSphere Application Server則允許Web應(yīng)用選擇類加載器使用的策略薯定。
假設(shè) 類加載器B2被要求裝載類MyClass,在parent delegation模型下瞳购,類加載器B2首先請(qǐng)求類加載器B代為裝載话侄,類加載器B再請(qǐng)求系統(tǒng)類裝載器去裝載MyClass,系統(tǒng)類裝載器也會(huì)繼續(xù)請(qǐng)求它的Parent擴(kuò)展類加載器去裝載MyClass学赛,以此類推直到引導(dǎo)類裝載器年堆。若引導(dǎo)類裝載器能成功裝載,則將MyClass所對(duì)應(yīng)的Class對(duì)象的reference逐層返回到類加載器B2盏浇,若引導(dǎo)類裝載器不能成功裝載变丧,下層的擴(kuò)展類裝載器將嘗試裝載,并以此類推直到類裝載器B2如果也不能成功裝載绢掰,則裝載失敗锄贷。
需要指出的是译蒂,Class Loader是對(duì)象,它的父子關(guān)系和類的父子關(guān)系沒(méi)有任何關(guān)系谊却。一對(duì)父子loader可能實(shí)例化自同一個(gè) Class,也可能不是哑芹,甚至父loader實(shí)例化自子類炎辨,子loader實(shí)例化自父類。
運(yùn)行時(shí)包
類加載器的一個(gè)重要用途是 在JVM中為相同名稱的Java類創(chuàng)建隔離空間聪姿。在JVM中碴萧,判斷兩個(gè)類是否相同,不僅是根據(jù)該類的二進(jìn)制名稱末购,還需要根據(jù)兩個(gè)類的定義類加載器破喻。 只有兩者完全一樣,才認(rèn)為兩個(gè)類的是相同的盟榴。
在允許兩個(gè)類型之間對(duì)包內(nèi)可見(jiàn)的成員進(jìn)行訪問(wèn)前曹质,虛擬機(jī)不但要確定這個(gè)兩個(gè)類型屬于同一個(gè)包,還必須確認(rèn)它們屬于同一個(gè)運(yùn)行時(shí)包-它們必須有同一個(gè)類裝載器裝載的擎场。 這樣羽德,java.lang.Virus和來(lái)自核心的java.lang的類不屬于同一個(gè)運(yùn)行時(shí)包,java.lang.Virus就不能訪問(wèn)JAVA API的java.lang包中的包內(nèi)可見(jiàn)的成員迅办。