Java類加載機(jī)制
1. 字節(jié)碼
源碼:test.java
package?Test;
/*這個(gè)文件主要用來(lái)做java課程作業(yè)*/
public?class?test?{
????public?static?void?main(String[]?args){
????????System.out.println("this?is?a?test");
????}
}
編譯成功后,用xxd test.class?命令可以查看一下這個(gè)字節(jié)碼文件
00000000:?cafe?babe?0000?0038?0010?0a00?0300?0d07??.......8........
00000010:?000e?0700?0f01?0006?3c69?6e69?743e?0100??..........
00000020:?0328?2956?0100?0443?6f64?6501?000f?4c69??.()V...Code...Li
00000030:?6e65?4e75?6d62?6572?5461?626c?6501?0012??neNumberTable...
00000040:?4c6f?6361?6c56?6172?6961?626c?6554?6162??LocalVariableTab
00000050:?6c65?0100?0474?6869?7301?000b?4c54?6573??le...this...LTes
00000060:?742f?7465?7374?3b01?000a?536f?7572?6365??t/test;...Source
00000070:?4669?6c65?0100?0974?6573?742e?6a61?7661??File...test.java
00000080:?0c00?0400?0501?0009?5465?7374?2f74?6573??........Test/tes
00000090:?7401?0010?6a61?7661?2f6c?616e?672f?4f62??t...java/lang/Ob
000000a0:?6a65?6374?0021?0002?0003?0000?0000?0001??ject.!..........
000000b0:?0001?0004?0005?0001?0006?0000?002f?0001??............./..
000000c0:?0001?0000?0005?2ab7?0001?b100?0000?0200??......*.........
000000d0:?0700?0000?0600?0100?0000?0400?0800?0000??................
000000e0:?0c00?0100?0000?0500?0900?0a00?0000?0100??................
000000f0:?0b00?0000?0200?0c????????????????????????.......
第一行cafe babe?被稱為“魔數(shù)”,是JVM識(shí)別.class文件到標(biāo)志。文件格式的定制者可以自由選擇魔數(shù)值(只要沒(méi)用過(guò)),比如說(shuō) .png 文件的魔數(shù)是?8950 4e47。
2. 類加載
類加載分為三個(gè)步驟:加載,連接,初始化。
2.1 加載:
類加載指的是將class文件讀入內(nèi)存,并為之創(chuàng)建一個(gè)
java.lang.Class?對(duì)象,即程序中使用的任何類時(shí),系統(tǒng)都會(huì)為之建立一個(gè)java.lang.Class?對(duì)象。系統(tǒng)中所有類都是由這個(gè)對(duì)象實(shí)現(xiàn)的。類的加載由類加載器完成,JVM提供的類加載器叫做系統(tǒng)類加載器(System Class-Loader),此外還可以通過(guò)繼承ClassLoader基類來(lái)自定義類加載器。
通??梢杂孟旅鎺追N方式加載類的二進(jìn)制數(shù)據(jù):
從本地文件系統(tǒng)加載class文件。
從jar包中加載class文件,如jar包的數(shù)據(jù)庫(kù)驅(qū)動(dòng)類。
通過(guò)網(wǎng)絡(luò)加載class文件。
把一個(gè)Java源文件動(dòng)態(tài)編譯并執(zhí)行加載。
2.2 連接
連接階段負(fù)責(zé)把類的二進(jìn)制數(shù)據(jù)合并到JRE中,其又可分為如下三個(gè)階段:
驗(yàn)證:確保加載到類信息符合JVM規(guī)范,無(wú)安全問(wèn)題。
準(zhǔn)備:為類的靜態(tài)Field分配內(nèi)存,并設(shè)置初始值。
解析:將類的二進(jìn)制數(shù)據(jù)中的符號(hào)引用替換成直接引用。
2.3 初始化
該階段主要是對(duì)靜態(tài)Field進(jìn)行初始化,在Java類中對(duì)靜態(tài)Field指定初始值有兩種方式:
聲明時(shí)即指定初始值,如?
staric int a = 5;用靜態(tài)代碼塊為靜態(tài)Field指定初始值,如
static { b = 5;}JVM初始化一個(gè)類包含如下步驟:
所以JVM總是最先初始化
java.lang.Object類2333333。如果這個(gè)類還沒(méi)被加載或連接,則程序先加載并連接該類。
如果該類的直接父類還沒(méi)被初始化,則先初始化其父類。
如果類中有初始化語(yǔ)句,則系統(tǒng)一次執(zhí)行這些初始化語(yǔ)句。
類初始化的時(shí)機(jī)(對(duì)類進(jìn)行主動(dòng)引用時(shí))
創(chuàng)建類的實(shí)例時(shí)(new,反射,反序列化)
調(diào)用某個(gè)類的靜態(tài)方法時(shí)。
使用某個(gè)類或接口的靜態(tài)Field或?qū)υ揊ield賦值時(shí)
使用反射來(lái)強(qiáng)制創(chuàng)建某個(gè)類或接口對(duì)應(yīng)的java.lang.Class對(duì)象,如
Class.forName("Person")初始化某個(gè)類的子類時(shí),此時(shí)該子類的所有父類都會(huì)被初始化。
直接使用java.exe運(yùn)行某個(gè)主類時(shí)。
3. 類加載器及加載機(jī)制
???java.lang.Object
???????????|__java.lang.ClassLoader
類加載器負(fù)責(zé)將class文件讀入內(nèi)存并為之生成對(duì)應(yīng)的java.lang.Class對(duì)象
對(duì)于任意一個(gè)類,都需要由它的類加載器和這個(gè)類本身一同確定其在 JVM 中的唯一性。也就是說(shuō),如果兩個(gè)類的加載器不同,即使兩個(gè)類來(lái)源于同一個(gè)字節(jié)碼文件,那這兩個(gè)類就必定不相等(兩個(gè)類的 Class 對(duì)象不?equals)。
Java類加載器可分為三種:
根(又叫啟動(dòng),引導(dǎo))類加載器(Bootstrap Class-Loader)
它負(fù)責(zé)加載Java核心類(比如String,System類)。它比較特殊,因?yàn)樗怯稍鶦++實(shí)現(xiàn)的,并不是
java.lang.ClassLoader?的子類,所以下面運(yùn)行為null。
public?class?TestJdkCl?{
????public?static?void?main(String[]?args)?{
????????System.out.println(String.class.getClassLoader());
????}
}擴(kuò)展類加載器(Extension Class-Loader),加載
jre/lib/ext?包下面的jar文件。我們可以通過(guò)把自己開(kāi)發(fā)的類打包成JAR文件放入擴(kuò)展目錄來(lái)為Java擴(kuò)展核心類以外的新功能。應(yīng)用(系統(tǒng))類加載器(Application or System Class-Loader),根據(jù)程序的類路徑(classpath)來(lái)加載Java類。
它負(fù)責(zé)在JVM啟動(dòng)時(shí)加載來(lái)自Java命令的-classpath選項(xiàng),java.class.path系統(tǒng)屬性,或CLASSPATH環(huán)境變量所指定的JAR包和類路徑。程序可以通過(guò)ClassLoader的靜態(tài)方法getSystemClassLoader來(lái)獲取系統(tǒng)類加載器:
例子:
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????ClassLoader?loader?=?Test.class.getClassLoader();
????????while?(loader?!=?null)?{
????????????System.out.println(loader.toString());
????????????loader?=?loader.getParent();
????????}
????}
}
每個(gè) Java 類都維護(hù)著一個(gè)指向定義它的類加載器的引用,通過(guò)?類名.class.getClassLoader()?可以獲取到此引用;然后通過(guò)?loader.getParent()?可以獲取類加載器的上層類加載器。
這段代碼的輸出結(jié)果如下:
jdk.internal.loader.ClassLoaders$AppClassLoader@3d4eac69
jdk.internal.loader.ClassLoaders$PlatformClassLoader@38af3868
第一行輸出為 Test 的類加載器,即應(yīng)用類加載器,它是?jdk.internal.loader.ClassLoaders$AppClassLoader?類的實(shí)例;第二行輸出為啟動(dòng)類加載器,是?jdk.internal.loader.ClassLoaders$PlatformClassLoader@38af3868?類的實(shí)例。
3.1 類加載機(jī)制

層級(jí)關(guān)系
Java類加載機(jī)制主要有以下三種:
雙親委派模型:如果一個(gè)類加載器收到了加載類的請(qǐng)求,它會(huì)先把請(qǐng)求委托給父類加載器去完成,依次遞歸,一直到最頂層的啟動(dòng)類加載器;只有在父類加載器無(wú)法完成類的加載工作時(shí),當(dāng)前類加載器才會(huì)自己去加載這個(gè)類。(注意:類加載器中的父子關(guān)系并不是類繼承上的父子關(guān)系,而是類加載器實(shí)例之間的關(guān)系)
全盤(pán)負(fù)責(zé):當(dāng)一個(gè)類加載器加載某個(gè)Class時(shí),該Class所依賴和引用的其他Class也將由該類加載器載入,除非顯式的使用另外一個(gè)類加載器來(lái)載入。
緩存機(jī)制:緩存機(jī)制會(huì)保證所有加載過(guò)的Class都會(huì)被緩存,當(dāng)程序中需要使用某個(gè)類時(shí),類加載器先從緩沖區(qū)搜索該類,若搜尋不到將讀取該類的二進(jìn)制數(shù)據(jù)并轉(zhuǎn)換成Class對(duì)象存入緩沖區(qū)中。這就是為什么修改了Class后需重啟JVM才能生效的原因。
JAVA9的改變

changed
可見(jiàn),在JDK 9中,應(yīng)用程序類加載器可以委托給平臺(tái)類加載器以及引導(dǎo)類加載器;平臺(tái)類加載器可以委托給引導(dǎo)類加載器和應(yīng)用程序類加載器。
此外,JDK 9不再支持?jǐn)U展機(jī)制。但是,它將擴(kuò)展類加載器保留在名為平臺(tái)類加載器的新名稱下。ClassLoader類包含一個(gè)名為getPlatformClassLoader()的靜態(tài)方法,該方法返回對(duì)平臺(tái)類加載器的引用。
在JDK 9之前,擴(kuò)展類加載器和應(yīng)用程序類加載器都是java.net.URLClassLoader類的一個(gè)實(shí)例。而在JDK 9中,平臺(tái)類加載器(以前的擴(kuò)展類加載器)和應(yīng)用程序類加載器是內(nèi)部JDK類的實(shí)例。如果你的代碼依賴于URLClassLoader類的特定方法,代碼可能會(huì)在JDK 9中崩潰。
JDK 9中的類加載機(jī)制有所改變。三個(gè)內(nèi)置的類加載器一起協(xié)作來(lái)加載類。
JDK 9中的類加載機(jī)制有所改變。三個(gè)內(nèi)置的類加載器一起協(xié)作來(lái)加載類。
JDK 9中的類加載機(jī)制有所改變。三個(gè)內(nèi)置的類加載器一起協(xié)作來(lái)加載類。
當(dāng)應(yīng)用程序類加載器需要加載類時(shí),它將搜索定義到所有類加載器的模塊。如果有合適的模塊定義在這些類加載器中,則該類加載器將加載類,這意味著應(yīng)用程序類加載器現(xiàn)在可以委托給引導(dǎo)類加載器和平臺(tái)類加載器。如果在為這些類加載器定義的命名模塊中找不到類,則應(yīng)用程序類加載器將委托給其父類,即平臺(tái)類加載器。如果類尚未加載,則應(yīng)用程序類加載器將搜索類路徑。如果它在類路徑中找到類,它將作為其未命名模塊的成員加載該類。如果在類路徑中找不到類,則拋出ClassNotFoundException異常。
當(dāng)平臺(tái)類加載器需要加載類時(shí),它將搜索定義到所有類加載器的模塊。如果一個(gè)合適的模塊被定義為這些類加載器中,則該類加載器加載該類。這意味著平臺(tái)類加載器可以委托給引導(dǎo)類加載器以及應(yīng)用程序類加載器。如果在為這些類加載器定義的命名模塊中找不到一個(gè)類,那么平臺(tái)類加載器將委托給它的父類,即引導(dǎo)類加載器。
當(dāng)引導(dǎo)類加載器需要加載一個(gè)類時(shí),它會(huì)搜索自己的命名模塊列表。如果找不到類,它將通過(guò)命令行選項(xiàng)-Xbootclasspath/a指定的文件和目錄列表進(jìn)行搜索。如果它在引導(dǎo)類路徑上找到一個(gè)類,它將作為其未命名模塊的成員加載該類。
?source:https://chenzhuo233.github.io/2019/09/28/JVM類加載機(jī)制/

喜歡,在看
