java classloader_Java Classloader原理分析
類的加載過程指通過一個(gè)類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流,并將其轉(zhuǎn)化為方法區(qū)的數(shù)據(jù)結(jié)構(gòu),進(jìn)而生成一個(gè)java.lang.Class對象作為方法區(qū)這個(gè)類各種數(shù)據(jù)訪問的入口。這個(gè)過程通過Java中的類加載器(ClassLoader)來完成。
類裝載器是用來把類(class)裝載進(jìn)JVM的。JVM規(guī)范定義了兩種類型的類裝載器:啟動(dòng)內(nèi)裝載器(bootstrap)和用戶自定義裝載器(user-defined class loader)。
一、Java默認(rèn)提供的三個(gè)ClassLoader
JVM在運(yùn)行時(shí)會(huì)產(chǎn)生三個(gè)ClassLoader:Bootstrap?ClassLoader、Extension?ClassLoader和AppClassLoader(System ClassLoader)。
1、?Bootstrap ClassLoader(啟動(dòng)類加載器)負(fù)責(zé)將%JAVA_HOME%/lib目錄中或-Xbootclasspath中參數(shù)指定的路徑中的,并且是虛擬機(jī)識別的(按名稱)類庫加載到JVM中。
也可以通過-Xbootclasspath參數(shù)定義。該ClassLoader不能被Java代碼實(shí)例化,因?yàn)樗荍VM本身的一部分。
2、Extension ClassLoader(擴(kuò)展類加載器)負(fù)責(zé)加載%JAVA_HOME%/lib/ext中的所有類庫;
只要jar包放置這個(gè)位置,就會(huì)被虛擬機(jī)加載。一個(gè)常見的、類似的問題是,你將mysql的低版本驅(qū)動(dòng)不小心放置在這兒,但你的Web應(yīng)用程序的lib下有一個(gè)新的jdbc驅(qū)動(dòng),但怎么都報(bào)錯(cuò),譬如不支持JDBC2.0的 DataSource,這時(shí)你就要當(dāng)心你的新jdbc可能并沒有被加載。這就是ClassLoader的delegate現(xiàn)象。常見的有l(wèi)og4j、 common-log、dbcp會(huì)出現(xiàn)問題,因?yàn)樗鼈兒苋菀妆蝗巳竭@個(gè)ext目錄,或是Tomcat下的common/lib目錄
3、Application ClassLoader:也稱為System ClassLoaer(加載%CLASSPATH%路徑的類庫)以及其它自定義的ClassLoader。缺省情況下,它是用戶創(chuàng)建的任何ClassLoader的父ClassLoader。
我們創(chuàng)建的standalone應(yīng)用的main class缺省情況下也是由它加載(通過Thread.currentThread().getContextClassLoader()查看)。實(shí)際開發(fā)中用ClassLoader更多時(shí)候是用其加載classpath下的資源,特別是配置文件,如ClassLoader.getResource(),比FileInputStream直接。
類加載器 classloader 是具有層次結(jié)構(gòu)的,也就是父子關(guān)系。其中,Bootstrap 是所有類加載器的父親。如下圖所示:
注意:?除了Java默認(rèn)提供的三個(gè)ClassLoader之外,用戶還可以根據(jù)需要定義自已的ClassLoader,而這些自定義的ClassLoader都必須繼承自java.lang.ClassLoader類,也包括Java提供的另外二個(gè)ClassLoader(Extension ClassLoader和App ClassLoader)在內(nèi),但是Bootstrap ClassLoader不繼承自ClassLoader,因?yàn)樗皇且粋€(gè)普通的Java類,底層由C++編寫,已嵌入到了JVM內(nèi)核當(dāng)中,當(dāng)JVM啟動(dòng)后,Bootstrap ClassLoader也隨著啟動(dòng),負(fù)責(zé)加載完核心類庫后,并構(gòu)造Extension ClassLoader和App ClassLoader類加載器。
二、雙親委托模型
Java中ClassLoader的加載采用了雙親委托機(jī)制,采用雙親委托機(jī)制加載類的時(shí)候采用如下的幾個(gè)步驟:
1、當(dāng)前ClassLoader首先從自己已經(jīng)加載的類中查詢是否此類已經(jīng)加載,如果已經(jīng)加載則直接返回原來已經(jīng)加載的類;
2、當(dāng)前classLoader的緩存中沒有找到被加載的類的時(shí)候,委托父類加載器去加載,父類加載器采用同樣的策略,首先查看自己的緩存,然后委托父類的父類去加載,一直到bootstrp ClassLoader.
3、當(dāng)所有的父類加載器都沒有加載的時(shí)候,再由當(dāng)前的類加載器加載,并將其放入它自己的緩存中,以便下次有加載請求的時(shí)候直接返回。
說到這里大家可能會(huì)想,Java為什么要采用這樣的委托機(jī)制?理解這個(gè)問題,我們引入另外一個(gè)關(guān)于Classloader的概念“命名空間”, 它是指要確定某一個(gè)類,需要類的全限定名以及加載此類的ClassLoader來共同確定。也就是說即使兩個(gè)類的全限定名是相同的,但是因?yàn)椴煌?ClassLoader加載了此類,那么在JVM中它是不同的類。明白了命名空間以后,我們再來看看委托模型。采用了委托模型以后加大了不同的 ClassLoader的交互能力,比如上面說的,我們JDK本生提供的類庫,比如hashmap,linkedlist等等,這些類由bootstrp 類加載器加載了以后,無論你程序中有多少個(gè)類加載器,那么這些類其實(shí)都是可以共享的,這樣就避免了不同的類加載器加載了同樣名字的不同類以后造成混亂。
JVM中類加載的機(jī)制——雙親委派模型。這個(gè)模型要求除了Bootstrap ClassLoader外,其余的類加載器都要有自己的父加載器。子加載器通過組合來復(fù)用父加載器的代碼,而不是使用繼承。在某個(gè)類加載器加載class文件時(shí),它首先委托父加載器去加載這個(gè)類,依次傳遞到頂層類加載器(Bootstrap)。如果頂層加載不了(它的搜索范圍中找不到此類),子加載器才會(huì)嘗試加載這個(gè)類。
當(dāng)JVM請求某個(gè)ClassLoader實(shí)例使用這種模型來加載某個(gè)類時(shí),首先檢查該類是否已經(jīng)被當(dāng)前類加載器加載,如果沒有被加載,則先委托給她的父類加載器即調(diào)用parent.loadClass()方法,這樣一直請求調(diào)用到請求頂層類加載ClassLoader#findBootStrapClassOrNull,如果這個(gè)方法依然加載不了,則會(huì)調(diào)用ClassLoader#findClass()方法,這個(gè)方法再找不到則會(huì)拋出ClassNotFoundException異常,但是這里的異常會(huì)被捕獲,然后返回給委托發(fā)起者,最后由當(dāng)前類加載器的findClass()方法類加載類,如果找不到則拋出ClassNotFoundException異常。
Class查找的位置和順序依次是:Cache、parent、self
三、ClassLoader加載類的原理
1、原理介紹
ClassLoader使用的是雙親委托模型來搜索類的,每個(gè)ClassLoader實(shí)例都有一個(gè)父類加載器的引用(不是繼承的關(guān)系,是一個(gè)包含的關(guān)系),虛擬機(jī)內(nèi)置的類加載器(Bootstrap ClassLoader)本身沒有父類加載器,但可以用作其它ClassLoader實(shí)例的的父類加載器。當(dāng)一個(gè)ClassLoader實(shí)例需要加載某個(gè)類時(shí),它會(huì)試圖親自搜索某個(gè)類之前,先把這個(gè)任務(wù)委托給它的父類加載器,這個(gè)過程是由上至下依次檢查的,首先由最頂層的類加載器Bootstrap ClassLoader試圖加載,如果沒加載到,則把任務(wù)轉(zhuǎn)交給Extension ClassLoader試圖加載,如果也沒加載到,則轉(zhuǎn)交給App ClassLoader 進(jìn)行加載,如果它也沒有加載得到的話,則返回給委托的發(fā)起者,由它到指定的文件系統(tǒng)或網(wǎng)絡(luò)等URL中加載該類。如果它們都沒有加載到這個(gè)類時(shí),則拋出ClassNotFoundException異常。否則將這個(gè)找到的類生成一個(gè)類的定義,并將它加載到內(nèi)存當(dāng)中,最后返回這個(gè)類在內(nèi)存中的Class實(shí)例對象。
2、為什么要使用雙親委托這種模型呢?
因?yàn)檫@樣可以避免重復(fù)加載,當(dāng)父親已經(jīng)加載了該類的時(shí)候,就沒有必要子ClassLoader再加載一次。考慮到安全因素,我們試想一下,如果不使用這種委托模式,那我們就可以隨時(shí)使用自定義的String來動(dòng)態(tài)替代java核心api中定義的類型,這樣會(huì)存在非常大的安全隱患,而雙親委托的方式,就可以避免這種情況,因?yàn)镾tring已經(jīng)在啟動(dòng)時(shí)就被引導(dǎo)類加載器(Bootstrcp ClassLoader)加載,所以用戶自定義的ClassLoader永遠(yuǎn)也無法加載一個(gè)自己寫的String,除非你改變JDK中ClassLoader搜索類的默認(rèn)算法。
3、JVM在搜索類的時(shí)候,如何判斷兩個(gè)class相同呢?
JVM在判定兩個(gè)class是否相同時(shí),不僅要判斷兩個(gè)類名是否相同,而且要判斷是否由同一個(gè)類加載器實(shí)例加載的。只有兩者同時(shí)滿足的情況下,JVM才認(rèn)為這兩個(gè)class是相同的。就算兩個(gè)class是同一份class字節(jié)碼,如果被兩個(gè)不同的ClassLoader實(shí)例所加載,JVM也會(huì)認(rèn)為它們是兩個(gè)不同class。
比如網(wǎng)絡(luò)上的一個(gè)Java類org.classloader.simple.NetClassLoaderSimple,javac編譯之后生成字節(jié)碼文件NetClassLoaderSimple.class,ClassLoaderA和ClassLoaderB這兩個(gè)類加載器并讀取了NetClassLoaderSimple.class文件,并分別定義出了java.lang.Class實(shí)例來表示這個(gè)類,對于JVM來說,它們是兩個(gè)不同的實(shí)例對象,但它們確實(shí)是同一份字節(jié)碼文件,如果試圖將這個(gè)Class實(shí)例生成具體的對象進(jìn)行轉(zhuǎn)換時(shí),就會(huì)拋運(yùn)行時(shí)異常java.lang.ClassCaseException,提示這是兩個(gè)不同的類型。
在一個(gè)單虛擬機(jī)環(huán)境下,標(biāo)識一個(gè)類有兩個(gè)因素:class的全路徑名、該類的ClassLoader。
4、ClassLoader 體系架構(gòu)
1、先檢查需要加載的類是否已經(jīng)被加載,這個(gè)過程是從下->上;
2、如果沒有被加載,則委托父加載器加載,如果加載不了,再由自己加載, 這個(gè)過程是從上->下
四、自定義ClassLoader
為什么我們需要自定義類加載?
主要原因:1、需要加載外部的Class,JVM提供的默認(rèn)ClassLoader只能加載指定目錄下的.jar和.class,如果我們想加載其它位置的class或者jar時(shí),這些默認(rèn)的類加載器是加載不到的(如果是文件格式必須配置到classpath)。例如:我們需要加載網(wǎng)絡(luò)上的一個(gè)class字節(jié)流;
2、需要實(shí)現(xiàn)Class的隔離性。目前我們常用的Web服務(wù)器,如tomcat、jetty都實(shí)現(xiàn)了自己定義的類加載,這些類加載主要完成以下三個(gè)功能:
A.實(shí)現(xiàn)加載Web應(yīng)用指定目錄下的jar和class
B.實(shí)現(xiàn)部署在容器中的Web應(yīng)用程共同使用的類庫的共享
C.實(shí)現(xiàn)部署在容器中各個(gè)Web應(yīng)用程序自己私有類庫的相互隔離
如何自定義類加載?
繼承java.lang.ClassLoader
覆寫父類的findClass()方法
Java除了上面所說的默認(rèn)提供的classloader以外,它還容許應(yīng)用程序可以自定義classloader,那么要想自定義classloader我們需要通過繼承java.lang.ClassLoader來實(shí)現(xiàn),接下來我們就來看看再自定義Classloader的時(shí)候,我們需要注意的幾個(gè)重要的方法:
1.loadClass 方法
loadClass method declare
public Class> loadClass(String name) throws ClassNotFoundException
上面是loadClass方法的原型聲明,上面所說的雙親委托機(jī)制的實(shí)現(xiàn)其實(shí)就實(shí)在此方法中實(shí)現(xiàn)的。下面我們就來看看此方法的代碼來看看它到底如何實(shí)現(xiàn)雙親委托的。
loadClass method implement
public Class>loadClass(String name) throws ClassNotFoundException
{return loadClass(name, false);
}
從上面可以看出loadClass方法調(diào)用了loadcClass(name,false)方法,那么接下來我們再來看看另外一個(gè)loadClass方法的實(shí)現(xiàn)。
Class loadClass(String name, boolean resolve)
protected synchronized Class>loadClass(String name, boolean resolve) throws ClassNotFoundException
{//First, check if the class has already been loaded Class c = findLoadedClass(name);//檢查class是否已經(jīng)被加載過了 if (c == null)
{try{if (parent != null) {
c= parent.loadClass(name, false); //如果沒有被加載,且指定了父類加載器,則委托父加載器加載。
}else{
c= findBootstrapClass0(name);//如果沒有父類加載器,則委托bootstrap加載器加載 }
}catch(ClassNotFoundException e) {//If still not found, then invoke findClass in order//to find the class.
c= findClass(name);//如果父類加載沒有加載到,則通過自己的findClass來加載。 }
}if(resolve)
{
resolveClass(c);
}returnc;
}
上面的代碼,通過注釋可以清晰看出loadClass的雙親委托機(jī)制是如何工作的。 這里我們需要注意一點(diǎn)就是public Class> loadClass(String name) throws ClassNotFoundException沒有被標(biāo)記為final,也就意味著我們是可以override這個(gè)方法的,也就是說雙親委托機(jī)制是可以打破的。另外上面注意到有個(gè)findClass方法,接下來我們就來說說這個(gè)方法到底是做什么的。
2.findClass
我們查看java.lang.ClassLoader的源代碼,我們發(fā)現(xiàn)findClass的實(shí)現(xiàn)如下:
protected Class>findClass(String name) throws ClassNotFoundException
{throw newClassNotFoundException(name);
}
我們可以看出此方法默認(rèn)的實(shí)現(xiàn)是直接拋出異常,其實(shí)這個(gè)方法就是留給我們應(yīng)用程序來override的。那么具體的實(shí)現(xiàn)就看你的實(shí)現(xiàn)邏輯了,你可以從磁盤讀取,也可以從網(wǎng)絡(luò)上獲取class文件的字節(jié)流,獲取class二進(jìn)制了以后就可以交給defineClass來實(shí)現(xiàn)進(jìn)一步的加載。defineClass我們再下面再來描述。通過上面的分析,我們可以得出如下結(jié)論:
3.defineClass
我們首先還是來看看defineClass的源碼:
defineClass
protected final Class> defineClass(String name, byte[] b, int off, intlen)
throws ClassFormatError
{return defineClass(name, b, off, len, null);
}
從上面的代碼我們看出此方法被定義為了final,這也就意味著此方法不能被Override,其實(shí)這也是jvm留給我們的唯一的入口,通過這個(gè)唯 一的入口,jvm保證了類文件必須符合Java虛擬機(jī)規(guī)范規(guī)定的類的定義。此方法最后會(huì)調(diào)用native的方法來實(shí)現(xiàn)真正的類的加載工作。
五、不遵循“雙親委托機(jī)制”的場景
上面說了雙親委托機(jī)制主要是為了實(shí)現(xiàn)不同的ClassLoader之間加載的類的交互問題,被大家公用的類就交由父加載器去加載,但是Java中確實(shí)也存在父類加載器加載的類需要用到子加載器加載的類的情況。下面我們就來說說這種情況的發(fā)生。
Java中有一個(gè)SPI(Service Provider Interface)標(biāo)準(zhǔn),使用了SPI的庫,比如JDBC,JNDI等,我們都知道JDBC需要第三方提供的驅(qū)動(dòng)才可以,而驅(qū)動(dòng)的jar包是放在我們應(yīng)用程序本身的classpath的,而jdbc 本身的api是jdk提供的一部分,它已經(jīng)被bootstrp加載了,那第三方廠商提供的實(shí)現(xiàn)類怎么加載呢?這里面JAVA引入了線程上下文類加載的概 念,線程類加載器默認(rèn)會(huì)從父線程繼承,如果沒有指定的話,默認(rèn)就是系統(tǒng)類加載器(AppClassLoader),這樣的話當(dāng)加載第三方驅(qū)動(dòng)的時(shí)候,就可 以通過線程的上下文類加載器來加載。另外為了實(shí)現(xiàn)更靈活的類加載器OSGI以及一些Java app server也打破了雙親委托機(jī)制。
另:啟動(dòng)時(shí)如果加上如下系統(tǒng)參數(shù),即可跟蹤JVM類的加載
-XX:+TraceClassLoading
與50位技術(shù)專家面對面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的java classloader_Java Classloader原理分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java spring框架 注解_详解J
- 下一篇: java 单引号的字符串类型_Java程