:全文都是自《深入理解JAVA虛擬機(jī)》一書中摘抄而來(lái)。
虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被JVM直接使用的Java類型,這就是JVM的類加載機(jī)制。
類的生命周期:
加載Loading
驗(yàn)證Verification
準(zhǔn)備preparation
解析Resolution
初始化Initialization
使用Using
卸載Unloading
連接包括:驗(yàn)證Verification、準(zhǔn)備preparation、解析Resolution
順序固定:加載Loading、驗(yàn)證Verification、準(zhǔn)備preparation、初始化Initialization、卸載Unloading
解析可以再初始化階段之后開始,這也是為了支持java語(yǔ)言的運(yùn)行時(shí)綁定
對(duì)于初始化階段,JVM明確規(guī)范了有且只有四種場(chǎng)景必須立即對(duì)類進(jìn)行"初始化"(而加載、驗(yàn)證、準(zhǔn)備自然要在這之前完成)。
1)遇到new、getstatic、putstatic、invokestatic這4條字節(jié)碼指令時(shí)。
new:實(shí)例化對(duì)象的時(shí)候
getstatic:讀取靜態(tài)字段的時(shí)候(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)
putstatic:設(shè)置靜態(tài)字段的時(shí)候(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)
invokestatic:調(diào)用靜態(tài)方法的時(shí)候
2)使用java.lang.reflect包反射調(diào)用類時(shí),如果類沒有初始化,則需要觸發(fā)其初始化。
3)當(dāng)初始化一個(gè)類時(shí),它有父類并且還沒有進(jìn)行初始化,則需要先觸發(fā)其父類的初始化。
4)當(dāng)JVM啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的類),虛擬機(jī)會(huì)先初始化這個(gè)主類。
這四種場(chǎng)景中的行為稱為對(duì)一個(gè)類進(jìn)行主動(dòng)引用。除此之外所有引用類的方式,都不會(huì)觸發(fā)初始化,稱為被動(dòng)引用。
加載Loading:
"加載"是"類加載"過(guò)程的一個(gè)階段,此階段中,JVM做了三件事:
1)通過(guò)一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流。
2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
3)在Java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這些數(shù)據(jù)的訪問(wèn)入口。(類的加載的最終產(chǎn)品是位于堆區(qū)中的Class對(duì)象。Class對(duì)象封裝了類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu),并且向Java程序員提供了訪問(wèn)方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口。)
針對(duì)于"通過(guò)一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流",JVM沒有限定二進(jìn)制字節(jié)流必須從要Class文件獲取或何處獲取,事實(shí)上二進(jìn)制字節(jié)流獲取方式多種多樣:
·zip包讀取。這很常見,最終成為日后JAR、EAR、WAR格式的基礎(chǔ)
·網(wǎng)絡(luò)中獲取。典型應(yīng)用:Applet
·運(yùn)行時(shí)計(jì)算生成。如動(dòng)態(tài)代理技術(shù),在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generatorProxyClass來(lái)為特定接口生成*$Proxy的代理類的二進(jìn)制字節(jié)流。
·由其他文件生成。典型場(chǎng)景:JSP引用
·從數(shù)據(jù)庫(kù)中讀取。不常見,有的中間件服務(wù)器(如SAP Netweaver)可以選擇把程序安裝到數(shù)據(jù)庫(kù)中完成程序代碼在集群中的分發(fā)。
......
加載階段(準(zhǔn)確地說(shuō)是加載階段中獲取類的二進(jìn)制字節(jié)流的動(dòng)作),是類加載過(guò)程中,在開發(fā)期可控性最強(qiáng)的階段。因?yàn)榧虞d階段既可以用系統(tǒng)提供的類加載器來(lái)完成,也可以用自定義的類加載器來(lái)完成,開發(fā)人員可以通過(guò)定義自己的類加載器去控制字節(jié)流的獲取方式。
針對(duì)于"通過(guò)一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流",JVM沒有限定二進(jìn)制字節(jié)流必須從要Class文件獲取或何處獲取
加載階段和連接階段的部分內(nèi)容(如一部分字節(jié)碼文件格式驗(yàn)證動(dòng)作)是交叉進(jìn)行的,加載階段尚未完成,連接階段可能已經(jīng)開始。
但是開始時(shí)間一定是先加載后連接的。
驗(yàn)證Verification:
驗(yàn)證是連接的第一步,這一階段的目的是為了確保Class文件的字節(jié)流包含的信息符合JVM的要求,并且不會(huì)危害JVM的安全。
如果驗(yàn)證的字節(jié)流不符合Class文件的存儲(chǔ)格式,就會(huì)拋出java.lang.varifyError錯(cuò)誤。
驗(yàn)證階段大致上會(huì)完成四個(gè)階段的檢驗(yàn)過(guò)程:
1)文件格式驗(yàn)證 : 驗(yàn)證字節(jié)流是否符合class文件格式的規(guī)范,并且是否能被當(dāng)前版本的JVM處理。
·是否以魔數(shù)0xCAFEBABE開頭
·主、次版本號(hào)是否在當(dāng)前JVM可處理范圍之內(nèi)。
·常量池中的常量是否有不被支持的常量類型(檢查常量tag標(biāo)志)。
·指向常量的各種索引值中是否有指向不存在的常量或不符類型的常量。
·CONSTANT_Utf8_info型的常量中是否有不符合UTF8編碼的數(shù)據(jù)。
...
2)元數(shù)據(jù)驗(yàn)證 : 驗(yàn)證字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析,確保符合java語(yǔ)言規(guī)范
·這個(gè)類是否有父類。(除了java.lang.Object之外,都應(yīng)該有父類。)
·這個(gè)類是否繼承了不能被繼承的類。(如被final修飾的類。)
·如果這個(gè)類不是抽象類,是否實(shí)現(xiàn)了其父類或父接口中要求實(shí)現(xiàn)的所有方法。
·類中的字段、方法是否和父類產(chǎn)生了矛盾。(例如覆蓋了父類的final字段,或出現(xiàn)了不符合規(guī)則的的方法重載,例如方法參數(shù)都一致,但是返回值類型卻不同等)
...
3)字節(jié)碼驗(yàn)證 : (最復(fù)雜)進(jìn)行數(shù)據(jù)流和控制流分析。元數(shù)據(jù)校驗(yàn)過(guò)后,對(duì)類的方法體進(jìn)行校驗(yàn)分析,確保類中方法不會(huì)危害JVM。
·保證任意時(shí)刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能夠配合工作。(防止出現(xiàn)此類情況:在操作數(shù)棧中放置了一個(gè)int,使用時(shí)卻按long類型來(lái)加載入本地變量表。)
·保證方法體中的類型轉(zhuǎn)換是有效的。例如可以向上轉(zhuǎn)型,將子類對(duì)象賦值給父類。但是父類賦值給子類或賦值給不相干的類就是不合法的、危險(xiǎn)的。
4)符號(hào)引用驗(yàn)證 : 對(duì)類自身以外的信息進(jìn)行匹配性的校驗(yàn)。發(fā)生在JVM將符號(hào)引用轉(zhuǎn)化為直接引用的時(shí)候(轉(zhuǎn)化動(dòng)作將在連接的第三個(gè)階段(解析階段)中發(fā)生)。
·符號(hào)引用中通過(guò)字符串描述的全限定名是否能夠找到對(duì)應(yīng)的類。
·在指定類中是否存在符合方法的字段描述符及簡(jiǎn)單名稱所描述的方法和字段。
·符號(hào)引用的類、字段、方法的訪問(wèn)性(private,default,protected,public)是否可被當(dāng)前類訪問(wèn)。
驗(yàn)證階段對(duì)于JVM的類加載機(jī)制來(lái)說(shuō),是一個(gè)非常重要的、但是不一定是必要的階段。如果所運(yùn)行的全部代碼都已經(jīng)被反復(fù)使用和驗(yàn)證過(guò),在實(shí)施階段就可以考慮使用-Xverify:none參數(shù)來(lái)關(guān)閉大部分的類驗(yàn)證措施,以縮短JVM類加載的時(shí)間。
準(zhǔn)備Preparation:
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配。
易混淆點(diǎn):
1、類變量?jī)H包含被static修飾的變量,不包含實(shí)例變量。實(shí)例變量將在對(duì)象初始化的時(shí)候隨著對(duì)象一起在堆中分配。
2、通常情況下,初始值是指0、null、false等各個(gè)數(shù)據(jù)類型的默認(rèn)值。
例:
public static int val = 123;
那么變量val在準(zhǔn)備階段過(guò)后的初始值應(yīng)該是0而不是123,因?yàn)檫@時(shí)候尚未開始執(zhí)行任何java方法,而把val賦值為123的putstatic指令是程序被編譯后,存放于類構(gòu)造器<clinit>()方法之中,所以把val賦值為123的動(dòng)作將在初始化階段才會(huì)被執(zhí)行。
上面提到"通常情況下",初始值是數(shù)據(jù)類型的默認(rèn)值,但是也有特殊情況:如果類字段地字段屬性表中存在ConstantValue屬性,那么準(zhǔn)備階段變量value就會(huì)被初始化為ConstantValue屬性所指定的值。例如上面的val加上final修飾:
public static final int val = 123;
那么變量階段JVM會(huì)把val的值設(shè)置成123。
解析Resolution:
解析階段是JVM將符號(hào)引用替換為直接引用的過(guò)程。
符號(hào)引用(Symbolic References):符號(hào)引用以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能無(wú)歧義地定位到目標(biāo)即可。符號(hào)引用與JVM實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中。
直接引用(Direct References):直接引用可以是直接指向目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。直接引用是與JVM實(shí)現(xiàn)的內(nèi)存布局相關(guān)的,同一個(gè)符號(hào)引用在不同的JVM實(shí)例上翻譯出來(lái)的直接引用一般不會(huì)相同。如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。
JVM規(guī)范之中并未規(guī)定解析階段發(fā)生的具體時(shí)間,只要求了在執(zhí)行anewarry、checkcast、getfield、getstatic、invokeinterface、invokespecial、invokestatic、invokevirtual、new、putfield、putstatic這13個(gè)用于操作符號(hào)引用的字節(jié)碼指令之前,先對(duì)他們所使用的符號(hào)引用進(jìn)行解析。所以JVM實(shí)現(xiàn)會(huì)根據(jù)需要來(lái)判斷,到底是類被加載器加載時(shí)就對(duì)常量池中的符號(hào)進(jìn)行引用解析,還是等到一個(gè)符號(hào)引用將要被使用前才去解析它。
解析動(dòng)作主要針對(duì)類或接口、字段、類方法、接口方法四類符號(hào)引用進(jìn)行,分別對(duì)應(yīng)常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info四種常量類型。
注:對(duì)同一個(gè)符號(hào)引用進(jìn)行多次解析是很正常的事情,JVM實(shí)現(xiàn)可能會(huì)對(duì)第一次解析的結(jié)果進(jìn)行緩存(在運(yùn)行時(shí)常量池中記錄直接引用,并把常量標(biāo)識(shí)為'已解析'狀態(tài))從而避免重復(fù)解析。無(wú)論是否會(huì)多次解析,JVM必須保證的是在同一個(gè)實(shí)體中,第一次成功則后續(xù)次次成功,第一次失敗,則后續(xù)一定失敗。
以下的解析均針對(duì)未被解析過(guò)的類、接口、字段、方法。以下的解析均針對(duì)未被解析過(guò)的類、接口、字段、方法。
1、類或接口的解析
假設(shè)當(dāng)前代碼所處的類是D,要把符號(hào)引用N解析成一個(gè)類或接口C的直接引用,三個(gè)步驟:
1)如果C不是一個(gè)數(shù)組,那JVM會(huì)把代表N的全限定名傳遞給D的類加載器去加載這個(gè)類C。在加載過(guò)程中,由于無(wú)數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證的需要,又將可能觸發(fā)其他相關(guān)類的加載動(dòng)作,例如加載這個(gè)類的父類或?qū)崿F(xiàn)的接口。一旦加載過(guò)程出現(xiàn)了異常,解析過(guò)程宣告失敗。
2)如果C是一個(gè)數(shù)組如Person[],并且數(shù)組元素是對(duì)象類型的,那JVM會(huì)按照1)中的規(guī)則去加載數(shù)組元素類型Person,接著由JVM生成一個(gè)代表此數(shù)組維度和元素的數(shù)組對(duì)象。
3)如果上面的步驟沒有出現(xiàn)異常,那么C在JVM中實(shí)際上已經(jīng)成為一個(gè)有效的類或接口了,但在解析完成之前還要進(jìn)行符號(hào)引用驗(yàn)證,確認(rèn)C是否具備對(duì)D的訪問(wèn)權(quán)限。如果沒有權(quán)限,將拋出java.lang.IllegalAccessError異常。
2、字段解析
JVM對(duì)字段解析之前,會(huì)先對(duì)字段表內(nèi)的class_index項(xiàng)中索引的CONSTANT_Class_info符號(hào)引用進(jìn)行解析。也就是說(shuō),會(huì)先對(duì)類或接口C的符號(hào)引用N解析成直接引用。后續(xù)繼續(xù)進(jìn)行字段的解析:
1)如果C本身就包含了簡(jiǎn)單名稱和字段描述符都與目標(biāo)相匹配的字段,則返回這個(gè)字段的直接引用,查找結(jié)束。
2)否則,如果在C中實(shí)現(xiàn)了接口,將會(huì)按照繼承關(guān)系從上往下遞歸搜索各個(gè)接口和他們的父接口,如果接口中包含了簡(jiǎn)單名稱和字段描述符都與目標(biāo)相匹配的字段,則返回這個(gè)字段的直接引用,查找結(jié)束。
3)否則,如果C不是java.lang.Object的話,將會(huì)按照繼承關(guān)系從上往下遞歸搜索其父類,如果父類中包含了簡(jiǎn)單名稱和字段描述符都與目標(biāo)相匹配的字段,則返回這個(gè)字段的直接引用,查找結(jié)束。
4)否則,查找失敗,拋出java.lang.NoSuchFieldError異常。
如果查找過(guò)程中成功返回了引用,將會(huì)對(duì)這個(gè)字段進(jìn)行權(quán)限驗(yàn)證,如果發(fā)現(xiàn)沒權(quán)限,將拋出java.lang.IllegalAccessError異常。
3、類方法解析
JVM在對(duì)方法解析之前,也是會(huì)先對(duì)方法所屬類或接口C的符號(hào)引用進(jìn)行解析。如果解析成功,JVM將會(huì)按照如下步驟對(duì)方法進(jìn)行解析:
1)類方法和接口方法符號(hào)引用的常量類型定義是分開的,如果在類方法表中發(fā)現(xiàn)class_index中索引的C是個(gè)接口,那就直接拋出java.lang.IncompatibleClassChangeError。異常。
2)如果通過(guò)了1,在類C中查找是否有簡(jiǎn)單名稱和描述符都與目標(biāo)相匹配的方法,如果有則返回這個(gè)方法的直接引用,查找結(jié)束。
3)否則,在類C的父類中遞歸查找是否有簡(jiǎn)單名稱和描述符都與目標(biāo)相匹配的方法,如果有則返回這個(gè)方法的直接引用,查找結(jié)束。
4)否則,在類C實(shí)現(xiàn)的接口列表及他們的父接口之中遞歸查找是否存在簡(jiǎn)單名稱和描述符都與目標(biāo)相匹配的方法,如果存在,則說(shuō)明C是一個(gè)抽象類,查找結(jié)束,拋出java.lang.NoSuchMethodError。
如果查找過(guò)程中成功返回了引用,將會(huì)對(duì)這個(gè)方法進(jìn)行權(quán)限驗(yàn)證,如果發(fā)現(xiàn)沒權(quán)限,將拋出java.lang.IllegalAccessError異常。
4、接口方法解析
JVM首先解析接口方法表的class_info項(xiàng)中索引的方法所屬的類或者接口C的符號(hào)引用。后續(xù):
1)與類方法解析相反,如果在接口方法表中發(fā)現(xiàn)class_index的索引C是個(gè)類,而不是接口,那就直接拋出java.lang.IncompatibleClassChangeError。異常。
2)如果通過(guò)了1,在接口C中查找是否有簡(jiǎn)單名稱和描述符都與目標(biāo)相匹配的方法,如果有則返回這個(gè)方法的直接引用,查找結(jié)束。
3)否則,在接口C的父接口中遞歸查找(直到并包含Object類)是否有簡(jiǎn)單名稱和描述符都與目標(biāo)相匹配的方法,如果有則返回這個(gè)方法的直接引用,查找結(jié)束。
4)否則,宣告方法查找失敗,拋出java.lang.NoSuchMethodError。
初始化Initialization:
類的初始化是類加載的最后一步,前面的類加載過(guò)程中,除了在加載階段用戶應(yīng)用程序可以通過(guò)自定義類加載器參與之外,其余動(dòng)作完全由JVM主導(dǎo)和控制。到了初始化階段,才開始執(zhí)行類中定義的Java程序代碼(或者說(shuō)是字節(jié)碼)。
在準(zhǔn)備階段,變量已經(jīng)賦值過(guò)一次系統(tǒng)要求的初始值。而在初始化階段,則是根據(jù)程序員通過(guò)程序制定的主觀計(jì)劃去初始化類變量和其他資源?;蛘呖梢詮牧硗庖粋€(gè)角度去表達(dá):初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過(guò)程。
下面是<clinit>()方法執(zhí)行過(guò)程中可能會(huì)影響程序運(yùn)行行為的一些特點(diǎn)和細(xì)節(jié):
1、<clinit>()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static{}塊)中的語(yǔ)句合并生成的,編譯器收集的順序是語(yǔ)句在源文件中出現(xiàn)的順序所決定的,靜態(tài)語(yǔ)句塊中只能訪問(wèn)到定義在靜態(tài)語(yǔ)句塊之前的變量,定義在他之后的變量,在前面的靜態(tài)語(yǔ)句塊中可以賦值,但不可以訪問(wèn)。
2、<clinit>()方法與類的構(gòu)造函數(shù)(或者說(shuō)實(shí)例構(gòu)造器<init>()方法)不同,它不需要顯式地調(diào)用父類構(gòu)造器,JVM會(huì)保證在子類的<clinit>()方法執(zhí)行之前,父類的<clinit>()方法已經(jīng)執(zhí)行完畢。因此在JVM中第一個(gè)被執(zhí)行的<clinit>()方法的類是肯定是Object。
3、由于父類的<clinit>()方法先執(zhí)行,也就意味著父類中定義的靜態(tài)語(yǔ)句塊要優(yōu)先于子類的變量賦值操作。
4、<clinit>()方法對(duì)于類或接口來(lái)說(shuō),不是必須的,如果一個(gè)類中沒有靜態(tài)語(yǔ)句塊,也沒有對(duì)變量的賦值操作,那么編譯器可以不為這個(gè)類生成<clinit>()方法。
5、接口中不能使用靜態(tài)語(yǔ)句塊,但仍然有變量初始化的賦值操作,因此接口與類一樣都會(huì)生成<clinit>()方法。但接口與類不同的是,執(zhí)行接口的<clinit>()方法不需要執(zhí)行父接口的<clinit>()方法。只有當(dāng)父接口中定義的變量被使用時(shí),父接口才會(huì)初始化。另外,接口的實(shí)現(xiàn)在初始化時(shí)也一樣不會(huì)執(zhí)行接口的<clinit>()方法。
6、JVM會(huì)保證一個(gè)類的<clinit>()方法在多線程環(huán)境中被正確地加鎖和同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的<clinit>()方法,其他線程都需要阻塞等待,知道活動(dòng)線程執(zhí)行<clinit>()方法完畢。如果在一個(gè)類的<clinit>()方法中有耗時(shí)很長(zhǎng)的操作,那就可能造成多個(gè)進(jìn)程阻塞,在實(shí)際應(yīng)用中這種阻塞往往是很隱蔽的。
類加載器:
JVM把類加載階段的“通過(guò)一個(gè)類的全限定名來(lái)獲取描述此類的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作放到j(luò)ava虛擬機(jī)外部去實(shí)現(xiàn),以便讓應(yīng)用程序自己去決定如何去獲取所需要的類。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊被稱為“類加載器”。
(一)類與類加載器
類加載器雖然只用于實(shí)現(xiàn)類的加載動(dòng)作,但它在java中起到的作用遠(yuǎn)遠(yuǎn)不僅于類加載階段。對(duì)于任何一個(gè)類來(lái)說(shuō),都需要類本身和加載這個(gè)類的類加載器一同確立其在java JVM中的唯一性。通俗來(lái)講:比較兩個(gè)類是否相等,只有兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義。否則,即使兩個(gè)類來(lái)自同一個(gè)class文件,但是由于加載他們的類加載器不同,那這兩個(gè)類就必不相等。
這里的“相等”,包括代表類的Class對(duì)象的equals()方法,isAssignableForm()方法和,isInstance()方法的返回結(jié)果。也包括instanceOf關(guān)鍵字對(duì)象所屬關(guān)系判定等情況。如果沒有注意到類加載器的影響,在某些情況下會(huì)產(chǎn)生迷惑人的結(jié)果。
如例:
// 用自定義類加載器加載類ClassLoaderTest,與系統(tǒng)默認(rèn)加載器加載的類進(jìn)行isInstance比較,結(jié)果為flase。
// 這是因?yàn)镴VM中存在了兩個(gè)ClassLoaderTest類,一個(gè)是由系統(tǒng)默認(rèn)類加載器加載的,一個(gè)是由自定義類加載器myloader加載的。
// 雖然都來(lái)自同一個(gè)class文件,但依然是兩個(gè)獨(dú)立的類。
// 說(shuō)明判定類相同的條件:1、類加載器相同。 2、class文件相同
public class ClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
try {
byte[] bytes = new byte[is.available()];
is.read(bytes);
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
};
/**
* loader 'ClassLoaderTest' by myLoader-
*/
Class<?> aClass = myLoader.loadClass("jvm.classloaderabout.classloaderandclass.ClassLoaderTest");
Object obj = aClass.newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof ClassLoaderTest);
}
}
(二)雙親委派模型
從JVM的角度來(lái)說(shuō),只存在兩種不同的類加載器:一種是啟動(dòng)類加載器(Bootstrap ClassLoader),這個(gè)類由C++實(shí)現(xiàn),是JVM自身的一部分。另外一種就是其他所有的類加載器,這些類加載器由java實(shí)現(xiàn)。獨(dú)立于JVM外部,并且全部都繼承自抽象類java.lang.ClassLoader。
從java開發(fā)者角度來(lái)說(shuō),常用的系統(tǒng)提供的類加載器可以細(xì)化有三種:
1、啟動(dòng)類加載器(Bootstrap ClassLoader):
這個(gè)類加載器負(fù)責(zé)將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數(shù)所指定的路徑中的,并且是JVM識(shí)別的(僅按照文件名識(shí)別,如rt.jar,名字不符合的類庫(kù)即使放在lib目錄中也不會(huì)被加載)類庫(kù)加載到JVM內(nèi)存中。啟動(dòng)類加載器無(wú)法被java程序直接引用。
2、擴(kuò)展類加載器(Extension ClassLoader):
這個(gè)類加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),它負(fù)責(zé)加載<JAVA_HOME>\LIB\ext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定的所有類庫(kù),開發(fā)者可以直接使用擴(kuò)展類加載器。
3、應(yīng)用程序類加載器(Application ClassLoader):
這個(gè)類加載器由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)。由于這個(gè)類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱之為系統(tǒng)類加載器。它負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫(kù),開發(fā)者可以直接用這個(gè)類加載器,如果應(yīng)用程序中沒有自定義過(guò)自己的類加載器,一般情況下這個(gè)就是程序中的默認(rèn)加載器。
我們的應(yīng)用程序都是由這三個(gè)類加載器相互配合進(jìn)行加載的,如果有必要,還可以加入自己定義的類加載器。加載器之間的關(guān)系一般如下:
啟動(dòng)類加載器(Bootstrap ClassLoader)<- 擴(kuò)展類加載器(Extension ClassLoader) <- 應(yīng)用程序類加載器(Application ClassLoader) <- 一個(gè)或多個(gè)自定義類加載器。
4、雙親委派模型
雙親委派模型要求:除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。這里的類加載器之間的父子關(guān)系一般不會(huì)以繼承的關(guān)系去實(shí)現(xiàn),而是都使用組合關(guān)系來(lái)復(fù)用父加載器的代碼。
類加載器的雙親委派模型在JDK1.2被引入并廣泛應(yīng)用于之后幾乎所有的java程序中。是java設(shè)計(jì)者們推薦使用的類加載實(shí)現(xiàn)方式,但是非強(qiáng)制性的約束模型。
雙親委派模型的工作過(guò)程:
如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)傳送到啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)去嘗試自己去加載。
使用雙親委派模型來(lái)組織類加載器之間的關(guān)系
好處:java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。例如Object類,它存放在rt.jar中,無(wú)論哪個(gè)類加載器要加載這個(gè)類,最終都是委派給啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載環(huán)境中都是同一個(gè)類。相反,如果沒有使用雙親委派模型,由各個(gè)類加載器去加載的話,如果用戶自己寫了一個(gè)Object類,并放在程序的ClassPath中,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類,Java類型體系中最基礎(chǔ)的行為也就無(wú)從保證,應(yīng)用程序也會(huì)變得一片混亂。
雙親委派模型對(duì)于保證java程序的穩(wěn)定運(yùn)作很重要,但是它的實(shí)現(xiàn)非常簡(jiǎn)單。實(shí)現(xiàn)雙親委派的代碼都在java.lang.ClassLoader的loadClass()方法之中。
邏輯清晰易懂:先檢查是否已經(jīng)被加載過(guò),若沒有加載則調(diào)用父加載器的loadClass()方法,若父類加載器為空,則默認(rèn)使用啟動(dòng)類加載器作為父加載器。如果父類加載失敗。則拋出ClassNotFoundException異常后,再調(diào)用自己的findClass()方法進(jìn)行加載。
JVM命令合集:
https://blog.csdn.net/qq_27785239/article/details/98657351