欧美+在线播放,蜜臀av在线,久久久99久久久国产自输拍,免费 黄 色 人成 视频 在 线,免费+国产+国产精品

 
您的位置:首頁(yè) >  新聞中心 > 云通訊公告
  云通訊公告
 

Java 類(lèi)加載機(jī)制詳解

來(lái)源:原創(chuàng)    時(shí)間:2017-11-03    瀏覽:0 次

什么是 Java 類(lèi)加載機(jī)制?
Java 虛擬機(jī)一般使用 Java 類(lèi)的流程為:首先將開(kāi)發(fā)者編寫(xiě)的 Java 源代碼(.java文件)編譯成 Java 字節(jié)碼(.class文件),然后類(lèi)加載器會(huì)讀取這個(gè) .class 文件,并轉(zhuǎn)換成 java.lang.Class 的實(shí)例。有了該 Class 實(shí)例后,Java 虛擬機(jī)可以利用 newInstance 之類(lèi)的方法創(chuàng)建其真正對(duì)象了。
ClassLoader 是 Java 提供的類(lèi)加載器,絕大多數(shù)的類(lèi)加載器都繼承自 ClassLoader,它們被用來(lái)加載不同來(lái)源的 Class 文件。
Class 文件有哪些來(lái)源呢?
上文提到了 ClassLoader 可以去加載多種來(lái)源的 Class,那么具體有哪些來(lái)源呢?
首先,最常見(jiàn)的是開(kāi)發(fā)者在應(yīng)用程序中編寫(xiě)的類(lèi),這些類(lèi)位于項(xiàng)目目錄下;
然后,有 Java 內(nèi)部自帶的核心類(lèi)如 java.lang、java.math、java.io 等 package 內(nèi)部的類(lèi),位于 $JAVA_HOME/jre/lib/ 目錄下,如 java.lang.String 類(lèi)就是定義在 $JAVA_HOME/jre/lib/rt.jar 文件里;
另外,還有 Java 核心擴(kuò)展類(lèi),位于 $JAVA_HOME/jre/lib/ext 目錄下。開(kāi)發(fā)者也可以把自己編寫(xiě)的類(lèi)打包成 jar 文件放入該目錄下;
最后還有一種,是動(dòng)態(tài)加載遠(yuǎn)程的 .class 文件。
既然有這么多種類(lèi)的來(lái)源,那么在 Java 里,是由某一個(gè)具體的 ClassLoader 來(lái)統(tǒng)一加載呢?還是由多個(gè) ClassLoader 來(lái)協(xié)作加載呢?
哪些 ClassLoader 負(fù)責(zé)加載上面幾類(lèi) Class?
實(shí)際上,針對(duì)上面四種來(lái)源的類(lèi),分別有不同的加載器負(fù)責(zé)加載。
首先,我們來(lái)看級(jí)別最高的 Java 核心類(lèi),即$JAVA_HOME/jre/lib 里的核心 jar 文件。這些類(lèi)是 Java 運(yùn)行的基礎(chǔ)類(lèi),由一個(gè)名為 BootstrapClassLoader 加載器負(fù)責(zé)加載,它也被稱(chēng)作 根加載器/引導(dǎo)加載器。注意,BootstrapClassLoader 比較特殊,它不繼承 ClassLoader,而是由 JVM 內(nèi)部實(shí)現(xiàn);
然后,需要加載 Java 核心擴(kuò)展類(lèi),即 $JAVA_HOME/jre/lib/ext 目錄下的 jar 文件。這些文件由 ExtensionClassLoader 負(fù)責(zé)加載,它也被稱(chēng)作 擴(kuò)展類(lèi)加載器。當(dāng)然,用戶(hù)如果把自己開(kāi)發(fā)的 jar 文件放在這個(gè)目錄,也會(huì)被 ExtClassLoader 加載;
接下來(lái)是開(kāi)發(fā)者在項(xiàng)目中編寫(xiě)的類(lèi),這些文件將由 AppClassLoader 加載器進(jìn)行加載,它也被稱(chēng)作 系統(tǒng)類(lèi)加載器 System ClassLoader;
最后,如果想遠(yuǎn)程加載如(本地文件/網(wǎng)絡(luò)下載)的方式,則必須要自己自定義一個(gè) ClassLoader,復(fù)寫(xiě)其中的 findClass() 方法才能得以實(shí)現(xiàn)。
因此能看出,Java 里提供了至少四類(lèi) ClassLoader 來(lái)分別加載不同來(lái)源的 Class。
那么,這幾種 ClassLoader 是如何協(xié)作來(lái)加載一個(gè)類(lèi)呢?
這些 ClassLoader 以何種方式來(lái)協(xié)作加載 String 類(lèi)呢?
String 類(lèi)是 Java 自帶的最常用的一個(gè)類(lèi),現(xiàn)在的問(wèn)題是,JVM 將以何種方式把 String class 加載進(jìn)來(lái)呢?
我們來(lái)猜想下。
首先,String 類(lèi)屬于 Java 核心類(lèi),位于 $JAVA_HOME/jre/lib 目錄下。有的朋友會(huì)馬上反應(yīng)過(guò)來(lái),上文中提過(guò)了,該目錄下的類(lèi)會(huì)由 BootstrapClassLoader 進(jìn)行加載。沒(méi)錯(cuò),它確實(shí)是由 BootstrapClassLoader 進(jìn)行加載。但,這種回答的前提是你已經(jīng)知道了 String 在 $JAVA_HOME/jre/lib 目錄下。
那么,如果你并不知道 String 類(lèi)究竟位于哪呢?或者我希望你去加載一個(gè) unknown 的類(lèi)呢?
有的朋友這時(shí)會(huì)說(shuō),那很簡(jiǎn)單,只要去遍歷一遍所有的類(lèi),看看這個(gè) unknown 的類(lèi)位于哪里,然后再用對(duì)應(yīng)的加載器去加載。
是的,思路很正確。那應(yīng)該如何去遍歷呢?
比如,可以先遍歷用戶(hù)自己寫(xiě)的類(lèi),如果找到了就用 AppClassLoader 去加載;否則去遍歷 Java 核心類(lèi)目錄,找到了就用 BootstrapClassLoader 去加載,否則就去遍歷 Java 擴(kuò)展類(lèi)庫(kù),依次類(lèi)推。
這種思路方向是正確的,不過(guò)存在一個(gè)漏洞。
假如開(kāi)發(fā)者自己偽造了一個(gè) java.lang.String 類(lèi),即在項(xiàng)目中創(chuàng)建一個(gè)包java.lang,包內(nèi)創(chuàng)建一個(gè)名為 String 的類(lèi),這完全可以做到。那如果利用上面的遍歷方法,是不是這個(gè)項(xiàng)目中用到的 String 不是都變成了這個(gè)偽造的 java.lang.String 類(lèi)嗎?如何解決這個(gè)問(wèn)題呢?
解決方法很簡(jiǎn)單,當(dāng)查找一個(gè)類(lèi)時(shí),優(yōu)先遍歷最高級(jí)別的 Java 核心類(lèi),然后再去遍歷 Java 核心擴(kuò)展類(lèi),最后再遍歷用戶(hù)自定義類(lèi),而且這個(gè)遍歷過(guò)程是一旦找到就立即停止遍歷。
在 Java 中,這種實(shí)現(xiàn)方式也稱(chēng)作 雙親委托。其實(shí)很簡(jiǎn)單,把 BootstrapClassLoader 想象為核心高層領(lǐng)導(dǎo)人, ExtClassLoader 想象為中層干部, AppClassLoader 想象為普通公務(wù)員。每次需要加載一個(gè)類(lèi),先獲取一個(gè)系統(tǒng)加載器 AppClassLoader 的實(shí)例(ClassLoader.getSystemClassLoader()),然后向上級(jí)層層請(qǐng)求,由最上級(jí)優(yōu)先去加載,如果上級(jí)覺(jué)得這些類(lèi)不屬于核心類(lèi),就可以下放到各子級(jí)負(fù)責(zé)人去自行加載。
如下圖所示:
真的是按照雙親委托方式進(jìn)行類(lèi)加載嗎?
下面通過(guò)幾個(gè)例子來(lái)驗(yàn)證上面的加載方式。
開(kāi)發(fā)者自定義的類(lèi)會(huì)被 AppClassLoader 加載嗎?
在項(xiàng)目中創(chuàng)建一個(gè)名為 MusicPlayer 的類(lèi)文件,內(nèi)容如下:
 
然后來(lái)加載 MusicPlayer。
 
打印結(jié)果為:
 
可以驗(yàn)證,MusicPlayer 是由 AppClassLoader 進(jìn)行的加載。
驗(yàn)證 AppClassLoader 的雙親真的是 ExtClassLoader 和 BootstrapClassLoader 嗎?
這時(shí)發(fā)現(xiàn) AppClassLoader 提供了一個(gè) getParent() 的方法,來(lái)打印看看都是什么。
打印結(jié)果為:
 
首先能看到 ExtClassLoader 確實(shí)是 AppClassLoader 的雙親,不過(guò)卻沒(méi)有看到 BootstrapClassLoader。事實(shí)上,上文就提過(guò), BootstrapClassLoader比較特殊,它是由 JVM 內(nèi)部實(shí)現(xiàn)的,所以 ExtClassLoader.getParent() = null。
如果把 MusicPlayer 類(lèi)挪到 $JAVA_HOME/jre/lib/ext 目錄下會(huì)發(fā)生什么?
上文中說(shuō)了,ExtClassLoader 會(huì)加載$JAVA_HOME/jre/lib/ext 目錄下所有的 jar 文件。那來(lái)嘗試下直接把 MusicPlayer 這個(gè)類(lèi)放到 $JAVA_HOME/jre/lib/ext 目錄下吧。
利用下面命令可以把 MusicPlayer.java 編譯打包成 jar 文件,并放置到對(duì)應(yīng)目錄。
 
這時(shí) MusicPlayer.jar 已經(jīng)被放置與 $JAVA_HOME/jre/lib/ext 目錄下,同時(shí)把之前的 MusicPlayer 刪除,而且這一次刻意使用 AppClassLoader 來(lái)加載:
 
打印結(jié)果為:
 
說(shuō)明即使直接用 AppClassLoader 去加載,它仍然會(huì)被 ExtClassLoader 加載到。
從源碼角度真正理解雙親委托加載機(jī)制
上面已經(jīng)通過(guò)一些例子了解了雙親委托的一些特性了,下面來(lái)看一下它的實(shí)現(xiàn)代碼,加深理解。
打開(kāi) ClassLoader 里的 loadClass() 方法,便是需要分析的源碼了。這個(gè)方法里做了下面幾件事:
檢查目標(biāo)class是否曾經(jīng)加載過(guò),如果加載過(guò)則直接返回;
如果沒(méi)加載過(guò),把加載請(qǐng)求傳遞給 parent 加載器去加載;
如果 parent 加載器加載成功,則直接返回;
如果 parent 未加載到,則自身調(diào)用 findClass() 方法進(jìn)行尋找,并把尋找結(jié)果返回。
代碼如下:
 
看完實(shí)現(xiàn)源碼相信能夠有更完整的理解。
類(lèi)加載器最酷的一面:自定義類(lèi)加載器
前面提到了 Java 自帶的加載器 BootstrapClassLoader、AppClassLoader和ExtClassLoader,這些都是 Java 已經(jīng)提供好的。
而真正有意思的,是 自定義類(lèi)加載器,它允許我們?cè)谶\(yùn)行時(shí)可以從本地磁盤(pán)或網(wǎng)絡(luò)上動(dòng)態(tài)加載自定義類(lèi)。這使得開(kāi)發(fā)者可以動(dòng)態(tài)修復(fù)某些有問(wèn)題的類(lèi),熱更新代碼。
下面來(lái)實(shí)現(xiàn)一個(gè)網(wǎng)絡(luò)類(lèi)加載器,這個(gè)加載器可以從網(wǎng)絡(luò)上動(dòng)態(tài)下載 .class 文件并加載到虛擬機(jī)中使用。
后面我還會(huì)寫(xiě)作與 熱修復(fù)/動(dòng)態(tài)更新 相關(guān)的文章,這里先學(xué)習(xí) Java 層 NetworkClassLoader 相關(guān)的原理。
作為一個(gè) NetworkClassLoader,它首先要繼承 ClassLoader;
然后它要實(shí)現(xiàn)ClassLoader內(nèi)的 findClass() 方法。注意,不是loadClass()方法,因?yàn)镃lassLoader提供了loadClass()(如上面的源碼),它會(huì)基于雙親委托機(jī)制去搜索某個(gè) class,直到搜索不到才會(huì)調(diào)用自身的findClass(),如果直接復(fù)寫(xiě)loadClass(),那還要實(shí)現(xiàn)雙親委托機(jī)制;
在 findClass() 方法里,要從網(wǎng)絡(luò)上下載一個(gè) .class 文件,然后轉(zhuǎn)化成 Class 對(duì)象供虛擬機(jī)使用。
具體實(shí)現(xiàn)代碼如下:
 
 
這個(gè)類(lèi)的作用是從網(wǎng)絡(luò)上(這里是本人的 local apache 服務(wù)器 http://localhost/java 上)目錄里去下載對(duì)應(yīng)的 .class 文件,并轉(zhuǎn)換成 Class<?> 返回回去使用。
下面我們來(lái)利用這個(gè) NetworkClassLoader 去加載 localhost 上的 MusicPlayer 類(lèi):
首先把 MusicPlayer.class 放置于 /Library/WebServer/Documents/java (MacOS)目錄下,由于 MacOS 自帶 apache 服務(wù)器,這里是服務(wù)器的默認(rèn)目錄;
執(zhí)行下面一段代碼:
 
正常運(yùn)行,加載 http://localhost/java/classloader/MusicPlayer.class成功。
可以看出 NetworkClassLoader 可以正常工作,如果讀者要用的話,只要稍微修改 url 的拼接方式即可自行使用。
小結(jié)
類(lèi)加載方式是 Java 上非常創(chuàng)新的一項(xiàng)技術(shù),給未來(lái)的熱修復(fù)技術(shù)提供了可能。本文力求通過(guò)簡(jiǎn)單的語(yǔ)言和合適的例子來(lái)講解其中雙親委托機(jī)制、自定義加載器等,并開(kāi)發(fā)了自定義的NetworkClassLoader。
當(dāng)然,類(lèi)加載是很有意思的技術(shù),很難覆蓋所有知識(shí)點(diǎn),比如不同類(lèi)加載器加載同一個(gè)類(lèi),得到的實(shí)例卻不是同一個(gè)等等。