您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“何為雙親委派原則”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“何為雙親委派原則”吧!
我敢打賭大家在開(kāi)發(fā)過(guò)程中經(jīng)常碰到一些類(lèi)加載的問(wèn)題,比如:
ClassNotFoundException
Cause: java.lang.ClassNotFoundException: Cannot find class: com.cc.A
NoClassDefFoundError
Cause: java.lang.NoClassDefFoundError: Cannot find class: com.cc.A
上述問(wèn)題均和java類(lèi)加載有關(guān),如果不清楚JVM中類(lèi)加載的原理,上述問(wèn)題會(huì)讓人郁悶至極,僥幸在網(wǎng)上找到解決方案也只是暫時(shí)解決問(wèn)題,后續(xù)在另外的場(chǎng)景中碰到又會(huì)繼續(xù)懵逼。
我這篇文章將對(duì) Java 類(lèi)加載器的雙親委派加載原理進(jìn)行闡述,并結(jié)合實(shí)例程序深究類(lèi)的雙親委派加載機(jī)制,大家徹底了解掌握類(lèi)加載原理,清楚了類(lèi)加載原理后,碰到上述類(lèi)似問(wèn)題就能快速解決,并在后續(xù)開(kāi)發(fā)中避免類(lèi)似問(wèn)題。
什么是Java類(lèi)加載?
java類(lèi)加載器負(fù)責(zé)將編譯好的 Java class 件加載到 Java 虛擬機(jī)(JVM)中的運(yùn)行時(shí)數(shù)據(jù)區(qū)中,供執(zhí)行引擎調(diào)用。
java類(lèi)加載在JVM體系結(jié)構(gòu)中的位置如圖所示:
jvm體系結(jié)構(gòu)原圖
沒(méi)有類(lèi)加載機(jī)制,編寫(xiě)的java程序就沒(méi)法在JVM中運(yùn)行,因此掌握java類(lèi)加載是非常重要的。
JVM類(lèi)加載層級(jí)關(guān)系
執(zhí)行java程序時(shí),會(huì)啟動(dòng)一個(gè)JVM進(jìn)程,JVM在啟動(dòng)時(shí)會(huì)做一些初始化操作,比如獲取系統(tǒng)參數(shù)等等,然后創(chuàng)建一個(gè)啟動(dòng)類(lèi)加載器,用于加載JVM運(yùn)行時(shí)必須的一些類(lèi)到內(nèi)存中,同時(shí)也會(huì)創(chuàng)建其他兩個(gè)類(lèi)加載器擴(kuò)展類(lèi)加載器和系統(tǒng)類(lèi)加載器。
啟動(dòng)類(lèi)加載器、擴(kuò)展類(lèi)加載器和系統(tǒng)類(lèi)加載器之間的關(guān)系如下圖所示:
jvm內(nèi)置classLoader
**啟動(dòng)類(lèi)加載器:**java虛擬機(jī)啟動(dòng)后創(chuàng)建的第一個(gè)類(lèi)加載器,由C++語(yǔ)言實(shí)現(xiàn),所以我們?cè)趈ava代碼中查看其信息時(shí),看到的均為null。
擴(kuò)展類(lèi)加載器:由啟動(dòng)類(lèi)加載器加載,并將擴(kuò)展類(lèi)加載器中的parent的值設(shè)置為null(表示指向啟動(dòng)類(lèi)加載器),同時(shí)繼承自URLClassLoader。
系統(tǒng)類(lèi)加載器:由啟動(dòng)類(lèi)加載器加載,并將系統(tǒng)類(lèi)加載期中的parent的值設(shè)置為上述創(chuàng)建的擴(kuò)展類(lèi)加載器。,同時(shí)繼承自URLClassLoader。
在代碼中可以通過(guò)如下方式查看類(lèi)加載中的parent指向:
代碼查看類(lèi)加載器的parent
注意:這里的parent不是java的繼承機(jī)制,而是類(lèi)加載器中的一個(gè)實(shí)例屬性,用于在類(lèi)加載時(shí)的委托對(duì)象,parent屬性定義在其所繼承的ClassLoader中,定義如下所示。
public abstract class ClassLoader { .................... // The parent class loader for delegation private final ClassLoader parent;
JVM類(lèi)加載的默認(rèn)加載路徑
每種類(lèi)型的類(lèi)加載器默認(rèn)都會(huì)有自己的加載路徑,啟動(dòng)類(lèi)加載器、擴(kuò)展類(lèi)加載器和系統(tǒng)類(lèi)加載器的默認(rèn)加載路徑如下圖所示:
三種類(lèi)加載器的加載路徑
如上圖所示:
1、啟動(dòng)類(lèi)加載器(BootClassLoader)由C++語(yǔ)言編寫(xiě),負(fù)責(zé)在JVM啟動(dòng)時(shí)加載jdk自身的一些核心class類(lèi)(jar包形式)到JVM中,加載時(shí)尋找資源的路徑由只讀系統(tǒng)屬性:”sun.boot.class.path“ 指定,一般為:”JAVA_HOME/jre/classes“目錄(在該目錄下只能放class文件,jar包形式文件不生效)。
查看啟動(dòng)類(lèi)加載類(lèi)加載路徑可以通過(guò)獲取系統(tǒng)屬性:”sun.boot.class.path“進(jìn)行查看,如圖所示:
lancher中設(shè)置啟動(dòng)類(lèi)加載路徑
啟動(dòng)類(lèi)加載器加載路徑
2、擴(kuò)展類(lèi)加載器(ExtClassLoader),負(fù)責(zé)加載位于系統(tǒng)屬性:"java.ext.dirs"指向的目錄下加載class文件(jar包或者直接class文件形式)到JVM中,比如通常ext類(lèi)加載路徑為:”$JAVA_HOMEx/jre/lib/ext“ 。
支持在JVM啟動(dòng)之前進(jìn)行修改路徑,運(yùn)行中修改路徑不生效,擴(kuò)展類(lèi)路徑中僅支持jar包的加載。
查看擴(kuò)展類(lèi)加載器的類(lèi)加載路徑可以通過(guò)獲取系統(tǒng)屬性:”java.ext.dirs“進(jìn)行查看或向上轉(zhuǎn)型為URLClassLoader(上面說(shuō)擴(kuò)展類(lèi)加載器繼承自URLClassLoader),查看位于父類(lèi)URLClassLoader中urls屬性的方式進(jìn)行查看,如圖所示:
擴(kuò)展類(lèi)加載器路徑
3、系統(tǒng)類(lèi)加載器(AppClassLoader),負(fù)責(zé)加載應(yīng)用classpath路徑下的class文件(jar包或者直接class文件形式)到JVM中,當(dāng)系統(tǒng)中沒(méi)有設(shè)置classpath路徑時(shí),默認(rèn)加載當(dāng)前路徑下的class文件。
查看系統(tǒng)類(lèi)加載器的類(lèi)加載路徑可以通過(guò)獲取系統(tǒng)屬性:”java.class.path“進(jìn)行查看或向上轉(zhuǎn)型為URLClassLoader上面說(shuō)擴(kuò)展類(lèi)加載器繼承自URLClassLoader),查看位于父類(lèi)URLClassLoader中urls屬性的方式進(jìn)行查看,如圖所示:
系統(tǒng)類(lèi)加載路徑
JVM類(lèi)加載雙親委托機(jī)制
JVM加載class類(lèi)文件到虛擬機(jī)時(shí),默認(rèn)首先采用系統(tǒng)類(lèi)加載器去加載用到的class類(lèi),采用的是雙親委托加載機(jī)制。
所謂雙親委托,顧名思義,就是當(dāng)前類(lèi)加載器(以系統(tǒng)類(lèi)加載器為例)在加載一個(gè)類(lèi)時(shí),委托給其雙親(注意這里的雙親指的是類(lèi)加載器中parent屬性指向的類(lèi)加載器)先進(jìn)行加載。
雙親類(lèi)加載器在加載時(shí)同樣委托給自己的雙親,如此反復(fù),直到某個(gè)類(lèi)加載器沒(méi)有雙親為止(通常情況下指雙親為null,也即為當(dāng)前的雙親為擴(kuò)展類(lèi)加載器,其parent為啟動(dòng)類(lèi)加載器),然后開(kāi)始在依次在各自的類(lèi)路徑下尋找、加載class類(lèi)。
如下圖所示:
雙親委派
雙親委托加載實(shí)例實(shí)例
采用JDK版本
java version "1.8.0_261" Java(TM) SE Runtime Environment (build 1.8.0_261-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)
本實(shí)例涉及到兩個(gè)類(lèi):TestMain.java 和 A.java,期中TestMain為啟動(dòng)類(lèi),在啟動(dòng)類(lèi)中調(diào)用類(lèi)A中的方法執(zhí)行進(jìn)行輸出,分別輸出啟動(dòng)類(lèi)和被依賴(lài)類(lèi)的類(lèi)加載器信息,類(lèi)定義如下所示:
A_java
TestMain
我們將兩個(gè)java文件拷貝到某個(gè)目錄下,在我本地比如放在E:\java_app目錄下,windows下打開(kāi)命令行窗口,切換到E:\java_app,對(duì)當(dāng)前java文件進(jìn)行編譯,執(zhí)行命令javac TestMain.java。
此時(shí)會(huì)在當(dāng)前目錄下生產(chǎn)對(duì)應(yīng)的class文件(這里只需要對(duì)TestMain執(zhí)行編譯命令,因?yàn)門(mén)estMain依賴(lài)了A,所以Jdk編譯器就會(huì)自動(dòng)先去編譯依賴(lài)的A),如圖所示:
編譯命令
接下來(lái)我們將觀察java類(lèi)加載機(jī)制是怎樣實(shí)現(xiàn)雙親委托加載的。
委托給擴(kuò)展類(lèi)加載器加載
由于擴(kuò)展類(lèi)在自身類(lèi)路徑下加載只支持尋找jar包的方式,因此我們通過(guò)工具將A.class文件打包進(jìn)A.jar。
然后將A.jar放置到擴(kuò)展類(lèi)加載路徑:$JAVA_HOME/jre/lib/ext,同時(shí)保留當(dāng)前目錄中的A.class文件。如圖所示:
擴(kuò)展委派
此時(shí)在當(dāng)前目錄:E:\java_app下仍然保留有A.class文件,在擴(kuò)展類(lèi)加載器路徑下多了一個(gè)包含了A.class的A.jar文件,在當(dāng)前目錄下執(zhí)行java命令執(zhí)行TestMain,命令為:java TestMain,輸出如下所示:
擴(kuò)展委派結(jié)果
由上圖輸出結(jié)果可知,class A雖然在系統(tǒng)類(lèi)加載器的加載路徑中,但由于類(lèi)加載的委托機(jī)制,A首先將由系統(tǒng)類(lèi)加載器委托給其雙親擴(kuò)展類(lèi)加載器進(jìn)行加載,剛好在擴(kuò)展類(lèi)加載器的加載路徑中包含了A.class(包含在A.jar中),所以A最終由擴(kuò)展類(lèi)加載器進(jìn)行了加載。
委托給啟動(dòng)類(lèi)加載器進(jìn)行加載
通常情況下,普通類(lèi)的加載不應(yīng)該委托給啟動(dòng)類(lèi)加載器進(jìn)行加載,因?yàn)榍懊嬲f(shuō)過(guò)啟動(dòng)類(lèi)加載器由C++實(shí)現(xiàn),在java虛擬機(jī)啟動(dòng)時(shí)生成的,在java環(huán)境中獲取她的信息均為null。
本實(shí)例為了探究類(lèi)加載的雙親委托機(jī)制,所以特意將構(gòu)造一個(gè)將普通類(lèi)委托給其加載的場(chǎng)景。
前面在講到啟動(dòng)類(lèi)加載器加載路徑時(shí)指出了啟動(dòng)類(lèi)加載器的加載路徑由只讀系統(tǒng)屬性”sun.boot.class.path“ 指定,且僅支持加載該目錄下固定的jar文件。
在jdk8中還有”$JAVA_HOME/jre/classes“目錄也是啟動(dòng)類(lèi)加載器加載的路徑(該路徑默認(rèn)可能不存在,可以手工創(chuàng)建一個(gè)),在該目錄下只能放class文件,jar包形式文件不生效。
因此,本實(shí)例程序?qū)?dāng)前目錄下的A.class文件拷貝到啟動(dòng)類(lèi)加載器的類(lèi)路徑:”$JAVA_HOME/jre/classes“中,同時(shí)保留當(dāng)前目錄中的A.class文件,也保留擴(kuò)展類(lèi)加載器類(lèi)路徑中的A.jar。
類(lèi)存放路徑如圖所示:
委派啟動(dòng)
在當(dāng)前目錄:E:\java_app目錄下執(zhí)行命令運(yùn)行TestMain,命令為:java TestMain,輸出如下所示:
委派啟動(dòng)結(jié)果
由上圖輸出結(jié)果可知,class A雖然在系統(tǒng)類(lèi)加載器的加載路徑中,也存在擴(kuò)展類(lèi)加載器的加載路徑中,但由于類(lèi)加載的委托機(jī)制,A首先將由系統(tǒng)類(lèi)加載器委托給其雙親擴(kuò)展類(lèi)加載器進(jìn)行加載。
擴(kuò)展類(lèi)加載器又會(huì)繼續(xù)進(jìn)行委托加載(實(shí)際上因?yàn)閿U(kuò)展類(lèi)加載器的parent:?jiǎn)?dòng)類(lèi)加載器為null,所以此時(shí)的委托動(dòng)作實(shí)際上就是去啟動(dòng)類(lèi)加載器的加載路徑中尋找class A),最終由啟動(dòng)類(lèi)加載進(jìn)行了A的加載。
雙親委托加載方向
類(lèi)加載器在加載類(lèi)時(shí),只能向上遞歸委托其雙親進(jìn)行類(lèi)加載,而不可能從雙親再反向委派當(dāng)前類(lèi)加載器來(lái)進(jìn)行類(lèi)加載。
在中國(guó)象棋中,卒子過(guò)河之后的行走軌跡永遠(yuǎn)只能是前進(jìn)或者左右平移,可以很形象的比作雙親委托類(lèi)加載的這種方向性。
卒子過(guò)河比喻當(dāng)前類(lèi)加載器委派其雙親加載了某個(gè)類(lèi)。這個(gè)類(lèi)的后續(xù)依賴(lài)的加載已經(jīng)和當(dāng)前類(lèi)加載器沒(méi)有關(guān)系。
過(guò)河之后的卒子只能前進(jìn),表示雙親在加載類(lèi)的依賴(lài)類(lèi)時(shí),只能繼續(xù)遞歸進(jìn)行雙親委派。
左右平移表示雙親在遞歸雙親委派加載失敗后,在雙親類(lèi)加載器自己的加載路徑中進(jìn)行加載。
為了表明委派具有方向性,我們繼續(xù)拿上面的TestMain.class和A.class兩個(gè)類(lèi)做實(shí)驗(yàn)。
上述委托實(shí)例中我們的場(chǎng)景時(shí)是:TestMain中依賴(lài)了A,我們將A通過(guò)雙親委托方式進(jìn)行了加載,本次實(shí)驗(yàn)中,我們將TestMain委托給雙親加載。
參照上述的操作步驟,將TestMain.class打進(jìn)TestMain.jar中,放到擴(kuò)展類(lèi)加載器的加載路徑中,同時(shí)也保留TestMain.class到當(dāng)前目錄,如下圖所示:
委派加載順序1
切換到當(dāng)前應(yīng)用目錄,執(zhí)行java命令運(yùn)行程序:java TestMain,執(zhí)行結(jié)果如下所示:
委派順序執(zhí)行結(jié)果
如上圖所示,出現(xiàn)錯(cuò)誤了,TestMain被擴(kuò)展類(lèi)加載器加載了,依賴(lài)的A卻沒(méi)有能被加載到。
原因就是上述說(shuō)的委派加載具有方向性導(dǎo)致的:
1、運(yùn)行java命令執(zhí)行TestMain程序時(shí),系統(tǒng)類(lèi)加載器準(zhǔn)備加載TestMain,根據(jù)雙親委派機(jī)制,先委派給其雙親進(jìn)行加載,最后,雙親擴(kuò)展類(lèi)加載器在其加載路徑中的TestMain.jar中找到了TestMain.class,完成了TestMain的加載。
2、TestMain中依賴(lài)了A,此時(shí),會(huì)根據(jù)加載了TestMain的類(lèi)加載器:擴(kuò)展類(lèi)加載器去加載A,加載方式根據(jù)委托機(jī)制遞歸委托給雙親加載,擴(kuò)展類(lèi)加載器的雙親為啟動(dòng)類(lèi)加載器,在啟動(dòng)類(lèi)加載器的加載路徑中不存在A,加載失敗,此時(shí)由擴(kuò)展類(lèi)加載器在自己的加載路徑中加載A,也因?yàn)榧虞d路徑中沒(méi)有A.class存在,A.class存在于系統(tǒng)類(lèi)加載器的加載路徑中,但是擴(kuò)展類(lèi)加載器不會(huì)再返回去委托系統(tǒng)類(lèi)加載器進(jìn)行加載,所以直接拋出加載失敗異常,出現(xiàn)了上述的錯(cuò)誤。
到此,相信大家對(duì)“何為雙親委派原則”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。