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