Java Classloader機(jī)制解析(轉(zhuǎn))

做Java開發(fā)编饺,對(duì)于ClassLoader的機(jī)制是必須要熟悉的基礎(chǔ)知識(shí)呀酸,本文針對(duì)Java ClassLoader的機(jī)制做一個(gè)簡(jiǎn)要的總結(jié)隆箩。因?yàn)椴煌腏VM的實(shí)現(xiàn)不同该贾,本文所描述的內(nèi)容均只限于Hotspot Jvm.

本文將會(huì)從JDK默認(rèn)的提供的ClassLoader,雙親委托模型捌臊,如何自定義ClassLoader以及Java中打破雙親委托機(jī)制的場(chǎng)景四個(gè)方面入手去討論和總結(jié)一下杨蛋。

JDK默認(rèn)ClassLoader

JDK 默認(rèn)提供了如下幾種ClassLoader

  1. Bootstrp loaderBootstrp加載器是用C++語言寫的,它是在Java虛擬機(jī)啟動(dòng)后初始化的理澎,它主要負(fù)責(zé)加載%JAVA_HOME%/jre/lib,Xbootclasspath
    參數(shù)指定的路徑以及%JAVA_HOME%/jre/classes中的類逞力。

  2. ExtClassLoader Bootstrp loader加載ExtClassLoader,并且將ExtClassLoader的父加載器設(shè)置為Bootstrp loader.ExtClassLoader是用Java寫的,具體來說就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader
    主要加載 %JAVA_HOME%/jre/lib/ext糠爬,此路徑下的所有classes目錄以及java.ext.dirs系統(tǒng)變量指定的路徑中類庫寇荧。

  3. AppClassLoader Bootstrp loader加載完 ExtClassLoader后,就會(huì)加載AppClassLoader,并且將AppClassLoader的父加載器指定為ExtClassLoader。AppClassLoader也是用Java寫成的, 它的實(shí)現(xiàn)類是sun.misc.Launcher$AppClassLoader ,另外我們知道ClassLoader中有個(gè)getSystemClassLoader 方法, 此方法返回的正是AppclassLoader.AppClassLoader主要負(fù)責(zé)加載classpath所指定的位置的
    類或者是jar文檔, 它也是Java程序默認(rèn)的類加載器执隧。

綜上所述揩抡,它們之間的關(guān)系可以通過下圖形象的描述:

雙親委托模型

Java中ClassLoader的加載采用了雙親委托機(jī)制,采用雙親委托機(jī)制加載類的時(shí)候采用如下的幾個(gè)步驟:
1 . 當(dāng)前ClassLoader首先從自己已經(jīng)加載的類中查詢是否此類已經(jīng)加載镀琉,如果已經(jīng)加載則直接返回原來已經(jīng)加載的類峦嗤。

每個(gè)類加載器都有自己的加載緩存,當(dāng)一個(gè)類被加載了以后就會(huì)放入緩存屋摔,等下次加載的時(shí)候就可以直接返回了烁设。

2 . 當(dāng)前classLoader的緩存中沒有找到被加載的類的時(shí)候,委托父類加載器去加載凡壤,父類加載器采用同樣的策略署尤,首先查看自己的緩存,然后委托父類的父類去加載亚侠,一直到bootstrp ClassLoader.

3 .當(dāng)所有的父類加載器都沒有加載的時(shí)候曹体,再由當(dāng)前的類加載器加載,并將其放入它自己的緩存中硝烂,以便下次有加載請(qǐng)求的時(shí)候直接返回箕别。

說到這里大家可能會(huì)想,Java為什么要采用這樣的委托機(jī)制滞谢?理解這個(gè)問題串稀,我們引入另外一個(gè)關(guān)于Classloader的概念“命名空間”, 它是指要確定某一個(gè)類狮杨,需要類的全限定名以及加載此類的ClassLoader來共同確定母截。也就是說即使兩個(gè)類的全限定名是相同的,但是因?yàn)椴煌?ClassLoader加載了此類橄教,那么在JVM中它是不同的類清寇。明白了命名空間以后喘漏,我們?cè)賮砜纯次心P汀2捎昧宋心P鸵院蠹哟罅瞬煌?ClassLoader的交互能力华烟,比如上面說的翩迈,我們JDK本生提供的類庫,比如hashmap,linkedlist等等盔夜,這些類由bootstrp 類加載器加載了以后负饲,無論你程序中有多少個(gè)類加載器,那么這些類其實(shí)都是可以共享的喂链,這樣就避免了不同的類加載器加載了同樣名字的不同類以后造成混亂返十。

如何自定義ClassLoader

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)方法匈仗,那么接下來我們?cè)賮砜纯戳硗庖粋€(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);  
  }  return c;
}

上面的代碼,我加了注釋通過注釋可以清晰看出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 new ClassNotFoundException(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我們?cè)傧旅嬖賮砻枋觥?ok陕凹,通過上面的分析悍抑,我們可以得出如下結(jié)論:

我們?cè)趯懽约旱腃lassLoader的時(shí)候,如果想遵循雙親委托機(jī)制捆姜,則只需要override findClass
.

3.defineClass

我們首先還是來看看defineClass的源碼:
defineClass

protected final Class<?> defineClass(String name, byte[] b, int off, int len)  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)真正的類的加載工作珊豹。

Ok,通過上面的描述簸呈,我們來思考下面一個(gè)問題:假如我們自己寫了一個(gè)java.lang.String的類,我們是否可以替換調(diào)JDK本身的類店茶?
答案是否定的蜕便。我們不能實(shí)現(xiàn)。為什么呢贩幻?我看很多網(wǎng)上解釋是說雙親委托機(jī)制解決這個(gè)問題轿腺,其實(shí)不是非常的準(zhǔn)確。因?yàn)殡p親委托機(jī)制是可以打破的丛楚,你完全可以自己寫一個(gè)classLoader來加載自己寫的java.lang.String類族壳,但是你會(huì)發(fā)現(xiàn)也不會(huì)加載成功,具體就是因?yàn)獒槍?duì)java.*開頭的類趣些,jvm的實(shí)現(xiàn)中已經(jīng)保證了必須由bootstrp來加載仿荆。

不遵循“雙親委托機(jī)制”的場(chǎng)景

上面說了雙親委托機(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ī)制惶凝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吼虎,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子苍鲜,更是在濱河造成了極大的恐慌思灰,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件混滔,死亡現(xiàn)場(chǎng)離奇詭異洒疚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)坯屿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門油湖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人领跛,你說我怎么就攤上這事乏德。” “怎么了吠昭?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵喊括,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我矢棚,道長(zhǎng)瘾晃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任幻妓,我火速辦了婚禮,結(jié)果婚禮上劫拢,老公的妹妹穿的比我還像新娘肉津。我一直安慰自己,他們只是感情好舱沧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布妹沙。 她就那樣靜靜地躺著,像睡著了一般熟吏。 火紅的嫁衣襯著肌膚如雪距糖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天牵寺,我揣著相機(jī)與錄音悍引,去河邊找鬼。 笑死帽氓,一個(gè)胖子當(dāng)著我的面吹牛趣斤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播黎休,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼浓领,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼玉凯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起联贩,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤漫仆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后泪幌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盲厌,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年座菠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狸眼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡浴滴,死狀恐怖拓萌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情升略,我是刑警寧澤微王,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站品嚣,受9級(jí)特大地震影響炕倘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜翰撑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一罩旋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧眶诈,春花似錦涨醋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至宪潮,卻和暖如春溯警,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狡相。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工梯轻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尽棕。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓檩淋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蟀悦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容