bootstrap Class Loaders負(fù)責(zé)裝載java.*下的基本類(lèi)?
extension Class Loaders負(fù)責(zé)裝載javax.*下的類(lèi)?
system Class Loaders負(fù)責(zé)系統(tǒng)(用戶)實(shí)現(xiàn)的類(lèi)?
三者的關(guān)系是?
bootstrap class loaders是extension class loaders的父親?
extension class loaders是system class loaders的父親?
class loader的裝載機(jī)制是parent delegate的模型。即?
類(lèi)的裝載是委托給父class loader去查找,如果沒(méi)有找到才用當(dāng)前的class loader來(lái)查找。
http://a123159521.iteye.com/blog/1094986
ClassLoader應(yīng)該是每一個(gè)Java程序員都必須了解的,但是我整整工作了四年才發(fā)現(xiàn)原來(lái)在這方面全是空白,現(xiàn)在在做模塊化,必須得了解這方面的知識(shí),模塊間必須做隔離.?
以下是我這段時(shí)候的學(xué)習(xí)和總結(jié),很多東西都是借網(wǎng)上的資料,JDK的ClassLoader的API,做如下總結(jié):?
1.類(lèi)加載器概述?
類(lèi)加載器是一個(gè)對(duì)象,是負(fù)責(zé)加載類(lèi).在JVM是通過(guò)類(lèi)加載器的調(diào)用LoadClass方法加載類(lèi)對(duì)象.?
類(lèi)加載器結(jié)構(gòu):?
1. 引導(dǎo)類(lèi)加載器(bootstrap class loader):它用來(lái)加載 Java 的核心庫(kù),是用原生代碼來(lái)實(shí)現(xiàn)的[null]?
2. 擴(kuò)展類(lèi)加載器(extensions class loader):它用來(lái)加載 Java 的擴(kuò)展庫(kù)。Java 虛擬機(jī)的實(shí)現(xiàn)會(huì)提供一個(gè)擴(kuò)展庫(kù)目錄。該類(lèi)加載器在此目錄里面查找并加載 Java 類(lèi)[ExtClassLoader]?
3. 系統(tǒng)類(lèi)加載器(system class loader):它根據(jù) Java 應(yīng)用的類(lèi)路徑(CLASSPATH)來(lái)加載 Java 類(lèi)。一般來(lái)說(shuō),Java 應(yīng)用的類(lèi)都是由它來(lái)完成加載的。可以通過(guò) ClassLoader.getSystemClassLoader() 來(lái)獲取它[AppClassLoader]?
2.類(lèi)加載器的工作流程?
?
當(dāng)classLoader有類(lèi)需要載入時(shí),先讓其parent查找載入,如果parent找不到,再由自己搜索路徑進(jìn)行載入。ClassLoader在運(yùn)行期會(huì)以父/子的層次結(jié)構(gòu)存在,每個(gè)classLoader 實(shí)例都有其父ClassLoader的引用,而父ClassLoader并沒(méi)有持有子ClassLoader的引用,從而形成一條單向鏈,當(dāng)一個(gè)類(lèi)裝載請(qǐng)求提交到某個(gè)ClassLoader時(shí),默認(rèn)的類(lèi)裝載過(guò)程如下:?
?1. 檢查這個(gè)類(lèi)有沒(méi)有被裝載過(guò),如果已經(jīng)裝載過(guò),返回?
?2. 調(diào)用父ClassLoader去裝載類(lèi),如果裝載成功返回.?
?3. 調(diào)用自身的裝載類(lèi)方法,如果裝載成功則返回?
?4. 如查都沒(méi)有成功,拋出ClassNotFoundException.?
簡(jiǎn)單說(shuō),當(dāng)ClassLoader鏈上的某一ClassLoader收到類(lèi)裝載請(qǐng)求時(shí),會(huì)按順序向上詢問(wèn)其所有父節(jié)點(diǎn),直到boot classLoader.任何一個(gè)節(jié)點(diǎn)成功受理了此請(qǐng)求,則返回,如果所有父節(jié)點(diǎn)都不能受理,這個(gè)時(shí)候才由請(qǐng)求的ClassLoader自身來(lái)裝載這個(gè)類(lèi),如果仍不能裝載,則拋出異常.?
3.Class的類(lèi)加載器到底是哪個(gè).?
類(lèi)加載器在其應(yīng)用場(chǎng)景的不同又可以分為如下類(lèi)加載器:?
? 1. 系統(tǒng) ClassLoader?
? 2. 調(diào)用者 ClassLoader?
? 3. 線程上下文ClassLoader?
這些類(lèi)加載器主要是用于動(dòng)態(tài)加載資源,也可以解決架包的重復(fù)問(wèn)題.?
調(diào)用者類(lèi)加載器
是指當(dāng)前所在的類(lèi)裝載時(shí)所使用的ClassLoader,它可能是SystemClassLoader, 也可能是一個(gè)自定義的ClassLoader.可以通過(guò)getClass().getClassLoader()來(lái)得到Caller ClassLoader.例如,存在類(lèi)A,是被AClassLoader所加載,A.class.getClassloader()為AClassLoader的實(shí)例,它就是A.class的Caller Classloader.??
如果在A類(lèi)中new一個(gè)B類(lèi),那么B類(lèi)的類(lèi)加載器就一定是AClassLoader嗎。答案是錯(cuò)的。因?yàn)閚ew一個(gè)對(duì)象,loadClass(B.class)可能在其父ClassLoader中就已經(jīng)完成.?
決定一個(gè)類(lèi)的類(lèi)加載器是defineClass,而判斷兩個(gè)類(lèi)是否為同一對(duì)象的標(biāo)準(zhǔn)里面有一條是類(lèi)加載器必須為相同.
?
現(xiàn)在有一個(gè)問(wèn)題,如何使用指定的ClassLoader去完成類(lèi)和資源的加載呢,或者說(shuō),當(dāng)需要去實(shí)例化一個(gè)調(diào)用者ClassLoader和它的父ClassLoader都不能加載的類(lèi)時(shí),怎么辦.?
一個(gè)典型的一例子是Jaxp,當(dāng)使用xerces的Sax實(shí)現(xiàn)時(shí),我們首先需要通過(guò)rt.jar中的java.xml.parsers.SaxparserFactory.getinstance()得到xeceImpl.jar中的org.apache.xerces.jaxp.SAXParserFactory.Impl的實(shí)例,由于Jaxp的框架接口的類(lèi)位于Java_hom/lib/rt.jar中,由bootStrap ClassLoader裝載,處于ClassLoader層次結(jié)構(gòu)中的最頂層,而xecesImpl.jar由低怪的ClassLoader裝載,也就是說(shuō)SaxParserFactoryImpl是在SaxParserFactory中實(shí)例化的,如前所述,使用SaxParserFactory的CallerClassLoader(boot)是完成不了這個(gè)任務(wù)的.這里我們需要理解下線程上下文ClassLoader.?
線程上下文ClassLoader
. 每一個(gè)線程都有一個(gè)關(guān)聯(lián)的上下文ClassLoader.如果使用new Thread()方式生成新的線程,新線程將繼承其父線程的上下文ClassLoader.如果程序?qū)€程上下文ClassLoader沒(méi)有任何改動(dòng)的話,程序的所有線程將都使用System ClassLoader作為上下文ClassLoader.當(dāng)使用Thread.currentThread().setContextClassLoader(classLoader)時(shí),線程上下文ClassLoader就變成了指定的ClassLoader了。此時(shí),在本線程的任意一處地方,調(diào)用Thread.currentThread().getContextClassLoader().都可以得到前面設(shè)置的ClassLoader.?
一個(gè)線程來(lái)了,第一件事情便是設(shè)置ClassLoader來(lái)設(shè)置線程上下文類(lèi)加載器,模塊內(nèi)部都使用線程上下文類(lèi)加載器[比如某一接口處于AClassLoader,我設(shè)置AClassLoader為線程上下文類(lèi)加載器,那么我通過(guò)getContextClassLoader就可以得到A類(lèi)的類(lèi)加載器,以后使用A類(lèi)的類(lèi)加載器,這樣就可以統(tǒng)一調(diào)用了]?
(有人可能會(huì)問(wèn)了,我總不能每加載一個(gè)類(lèi),都使用上下文線程去加載吧,我笑了,其實(shí)這大可不必,只要你的類(lèi)是你私有的[在當(dāng)前類(lèi)加載器父級(jí)加載器未加載過(guò)],就不需要重新加載)?
4.JVM工作流程?
Class Loader 加載流程?
? Jvm 建立=>初始化工作=>產(chǎn)生第一個(gè)ClassLoader,即boot?
? Boot ClassLoader在sum.misc.Launcher類(lèi)里面的ExtClassLoader,并設(shè)置其Parent為Boot.?
? Boot ClassLoader載入sun.misc.Launcher$AppClassLoader,設(shè)定其parent為ExtClassLoader(但是AppClassLoader也是boot所載入)?
? AppClassLoader載入各個(gè)xx.class,xx.class也有可能被ExtClassLoader或者boot載入.?
? 自定義的ClassLoader的getparent()是AppClassLoader.parent和他的加載器沒(méi)有關(guān)系.?
? ExtClassLoader和AppClassLoader都是URLClassLoader的子類(lèi)。?
? AppClassLoader的URL是由系統(tǒng)參數(shù)java.class.path取出的字符串決定,而java.class.path由運(yùn)行機(jī)制java.exe時(shí)的-cp或-classpath或CLASSPATH環(huán)境變量決定?
? ExtClassLoader查找的url是系統(tǒng)變量java.ext.dirs,java.ext.dirs默認(rèn)為jdk\jre\lib\ext?
? Bootstrap loader的查找url是sun.boot.class.path?
5.獨(dú)立應(yīng)用的類(lèi)加載器?
由于系統(tǒng)類(lèi)加載器是JVM最后創(chuàng)建的類(lèi)加載器,這樣代碼只會(huì)適應(yīng)于簡(jiǎn)單命令行啟動(dòng)的程序。一旦代碼移植到EJB、Web應(yīng)用或者Java Web Start應(yīng)用程序中,程序肯定不能正確執(zhí)行。?
因此一般只有兩種選擇,當(dāng)前類(lèi)加載器和線程上下文類(lèi)加載器。當(dāng)前類(lèi)加載器是指當(dāng)前方法所在類(lèi)的加載器。這個(gè)類(lèi)加載器是運(yùn)行時(shí)類(lèi)解析使用的加載器,Class.forName(String)和Class.getResource(String)也使用該類(lèi)加載器。代碼中X.class的寫(xiě)法使用的類(lèi)加載器也是這個(gè)類(lèi)加載器。?
Web應(yīng)用和Java企業(yè)級(jí)應(yīng)用中,應(yīng)用服務(wù)器經(jīng)常要使用復(fù)雜的類(lèi)加載器結(jié)構(gòu)來(lái)實(shí)現(xiàn)JNDI(Java命名和目錄接口)、線程池、組件熱部署等功能,因此理解這一點(diǎn)尤其重要。?
通常JVM中的類(lèi)加載器是按照層次結(jié)構(gòu)組織的,目的是每個(gè)類(lèi)加載器(除了啟動(dòng)整個(gè)JVM的原初類(lèi)加載器)都有一個(gè)父類(lèi)加載器。當(dāng)類(lèi)加載請(qǐng)求到來(lái)時(shí),類(lèi)加載器通常首先將請(qǐng)求代理給父類(lèi)加載器。只有當(dāng)父類(lèi)加載器失敗后,它才試圖按照自己的算法查找并定義當(dāng)前類(lèi)。[如何覆蓋父類(lèi)的加載機(jī)制]?
有時(shí)這種模式并不能總是奏效。這通常發(fā)生在JVM核心代碼必須動(dòng)態(tài)加載由應(yīng)用程序動(dòng)態(tài)提供的資源時(shí)。拿JNDI為例,它的核心是由JRE核心類(lèi)(rt.jar)實(shí)現(xiàn)的。但這些核心JNDI類(lèi)必須能加載由第三方廠商提供的JNDI實(shí)現(xiàn)。這種情況下調(diào)用父類(lèi)加載器(原初類(lèi)加載器)來(lái)加載只有其子類(lèi)加載器可見(jiàn)的類(lèi),這種代理機(jī)制就會(huì)失效[雙親委托機(jī)制]。?
解決辦法就是讓核心JNDI類(lèi)使用線程上下文類(lèi)加載器,從而有效的打通類(lèi)加載器層次結(jié)構(gòu),逆著代理機(jī)制的方向使用類(lèi)加載器。?
順便提一下,XML解析API(JAXP)也是使用此種機(jī)制。當(dāng)JAXP還是J2SE擴(kuò)展時(shí),XML解析器使用當(dāng)前累加載器方法來(lái)加載解析器實(shí)現(xiàn)。但當(dāng)JAXP成為J2SE核心代碼后,類(lèi)加載機(jī)制就換成了使用線程上下文加載器,這和JNDI的原因相似。?
但這在不同JVM線程共享數(shù)據(jù)來(lái)溝通時(shí),就會(huì)使類(lèi)加載器的結(jié)構(gòu)亂七八糟。除非所有線程都使用同一個(gè)上下文類(lèi)加載器。而且,使用當(dāng)前類(lèi)加載器已成為缺省規(guī)則,它們廣泛應(yīng)用在類(lèi)聲明、Class.forName等情景中。即使你想盡可能只使用上下文類(lèi)加載器,總是有這樣那樣的代碼不是你所能控制的。這些代碼都使用代理到當(dāng)前類(lèi)加載器的模式?;祀s使用代理模式是很危險(xiǎn)的。?
這種混亂的狀況還將在Java中存在很長(zhǎng)時(shí)間。在J2SE中還包括以下的功能使用不同的類(lèi)加載器:?
? JNDI使用線程上下文類(lèi)加載器?
? Class.getResource()和Class.forName()使用當(dāng)前類(lèi)加載器?
? JAXP使用上下文類(lèi)加載器?
? Java.unit.ResourceBundle使用調(diào)用者的當(dāng)前類(lèi)加載器?
? URL協(xié)議處理器使用java.protocol.handler.pkgs系統(tǒng)屬性并只使用系統(tǒng)類(lèi)加載器?
? Java序列化API缺省使用調(diào)用者當(dāng)前的類(lèi)加載器.?
6.Tomcate類(lèi)加載器的代理模式?
下面是對(duì)每個(gè)類(lèi)加載器的定義:?
1.Bootstrap加載器在這里是Java里的Bootstrap和ExtClassLoader的總稱(chēng),負(fù)責(zé)加載Java核心包的類(lèi),和<Java_Home>/jre/lib/ext目錄下的類(lèi).通常我們開(kāi)發(fā)人員并不關(guān)心.我想只要是java程序這些肯定是必要的?
2.System就是系統(tǒng)加載器,一般是AppClassLoader,負(fù)責(zé)加載ClassPath環(huán)境變量設(shè)置目錄下的值,這個(gè)我們開(kāi)發(fā)人員會(huì)非常關(guān)注,但是在Tomcat里面,雖然用AppClassLoader類(lèi)加載器,但我們?cè)O(shè)置的ClassPath對(duì)它沒(méi)有影響(如果有影響,那就麻煩了,將會(huì)導(dǎo)致Tomcat運(yùn)行不穩(wěn)定),為什么呢,因?yàn)閠omcat每次啟動(dòng)的時(shí)候都會(huì)在命令行窗口中都會(huì)重新設(shè)置Classpath值為:<catalina_Home>/bin/bootstrap.jar和<java_Home>/lib/tools.jar,所以這里面的類(lèi)一般對(duì)應(yīng)用程序不可見(jiàn)的.除非你設(shè)置了?
3.Common類(lèi)加載器負(fù)責(zé)加載TomcatHOME/common/class下的.Class文件和common/lib中的jar包,這些類(lèi)可以被Tomcat內(nèi)核和每個(gè)Web應(yīng)用程序都可以看見(jiàn),一般放公用的一些重要的類(lèi),如servlet.jar等?
4.Catalina類(lèi)加載器從server/classes和server/lib下加載類(lèi),Catalina加載的類(lèi)只對(duì)Tomcat服務(wù)器內(nèi)核可見(jiàn),對(duì)Web應(yīng)用程序不可見(jiàn),對(duì)于運(yùn)行Tomcat內(nèi)核的線程,它的上下文類(lèi)加載器就是Catalina類(lèi)加載器?
5.Shared類(lèi)加載器負(fù)責(zé)從share/classes和share/lib中加載類(lèi),它加載的類(lèi)只對(duì)所有Web應(yīng)用程序有效,對(duì)Tomcat不可見(jiàn).?
6.WebappX類(lèi)加載器負(fù)責(zé)加載Web應(yīng)用程序的/web-INF/classes和lib目錄下的類(lèi),只對(duì)當(dāng)前Web應(yīng)用程序有效,對(duì)其他Web應(yīng)用程序無(wú)效,對(duì)于運(yùn)行每個(gè)Web應(yīng)用程序的線程,他們的上下文類(lèi)加載器就是它們各自的WebappX類(lèi)加載器?
7.自定義類(lèi)加載器?
通過(guò)ClassLoader的子類(lèi)動(dòng)態(tài)加載class文件,體現(xiàn)java動(dòng)態(tài)實(shí)時(shí)類(lèi)裝入特性:?
ClassLoader有兩種載入方式:?
? Pre-loading 預(yù)先載入,載入的基礎(chǔ)類(lèi)?
? Load-on-demand 按需載入,只有實(shí)例化一個(gè)類(lèi)才會(huì)被classloader載入,僅僅聲有不會(huì)被載入.?
Static 何時(shí)執(zhí)行:?
? 當(dāng)調(diào)用forName(String)載入class時(shí)執(zhí)行,如果調(diào)用ClassLoader.loadClass不會(huì)執(zhí)行,forName(String,false,ClassLoader)也不會(huì)執(zhí)行.?
? 如果在載入class時(shí)沒(méi)有執(zhí)行static塊,則在第一次實(shí)例化時(shí)執(zhí)行,比如new,Class.newInstance()操作.?
? Static塊僅執(zhí)行一次?
?
當(dāng)使用java 去執(zhí)行一個(gè)類(lèi)的時(shí)候,JVM使用applicationClassLoader加載這個(gè)類(lèi),如果A類(lèi)引用了B類(lèi),不管是直接引用,還是class.forName()引用,JVM會(huì)找到加載A類(lèi)的classLoader,并使用這個(gè)ClassLoader加載B類(lèi).?
注意:JVM加載類(lèi)A,并使用A的ClassLoader去加載B,但B的類(lèi)加載器并不一定和A的類(lèi)加載器一致.?
使用java –verbos:class Main運(yùn)行一個(gè)程序,加載如下:?
1. 加載java*下的類(lèi)?
?
2. 加載自定義的類(lèi)?
?
只要當(dāng)程序運(yùn)行到B了,需要加載B了,JVM才會(huì)去加載這個(gè)類(lèi)?
隱式加載?
這里的B就是引用類(lèi),發(fā)生由于引用,實(shí)例化或繼承導(dǎo)致需要裝載類(lèi)的時(shí)候,隱式類(lèi)裝載是在幕后啟動(dòng)的,JVM會(huì)解析必要的引用并裝載類(lèi).?
顯式加載?
1. Java.lang.Class.forName()方法加載?
a) Public static Class forName(String classname)?
b) Public static Class forName(String className,Boolean ini,ClassLoader loader)?
參數(shù)說(shuō)明:?
className 所需類(lèi)的完全限定名?
ini 是否必須初始化類(lèi)(靜態(tài)代理初始化)?
loader – 用于加載類(lèi)的類(lèi)加載器?
調(diào)用只有一個(gè)參數(shù)的相當(dāng)于Class.forName(className,true,loader);?
這里的loader 為callerClassLoader,就是調(diào)用者類(lèi)加載器.?
不管使用的是new來(lái)實(shí)例化某個(gè)類(lèi),或是使用只有一個(gè)參數(shù)的forName()方法,內(nèi)部矛盾都隱含了“載入類(lèi)+運(yùn)行靜態(tài)代碼塊”的步驟。而使用三個(gè)參數(shù)的,如果第二個(gè)參數(shù)為false,那么類(lèi)加載器只會(huì)加載類(lèi),而不會(huì)初始化靜態(tài)代碼塊.只有實(shí)例化類(lèi)時(shí),才會(huì)執(zhí)行.不過(guò)靜態(tài)代碼只執(zhí)行一次.?
以上是使用自定義加載器必須了解的.自定義加載器見(jiàn)下一篇?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(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ì)您有幫助就好】元

