本篇內(nèi)容:
- 類加載過程
- 類加載器分類
- 雙親委托機(jī)制
- 沙箱安全機(jī)制

類加載過程
類加載過程
類加載器子系統(tǒng)負(fù)責(zé)將從文件系統(tǒng)或者網(wǎng)絡(luò)中加載Class文件到內(nèi)存中。jvm通過按需加載的方式加載class文件,并且只加載一次。加載的類信息存放在方法區(qū)的內(nèi)存空間,除了類信息外,方法區(qū)還會存放運(yùn)行時常量池信息,可能還包括字符串字面值或數(shù)字常量(這部分常量信息是Class文件中常量池部分的內(nèi)存映射)。
類的加載過程:加載->鏈接(驗(yàn)證、準(zhǔn)備、解析)->初始化
- 加載:通過類加載器將.class文件加載進(jìn)內(nèi)存,在內(nèi)存中生成一個Class對象(java.lang.Class),作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。
虛擬機(jī)在加載階段做了三件事情:(1)通過一個類的全類名來獲取此類的二進(jìn)制字節(jié)流。(2)將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化成方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)。(3)在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口 - 驗(yàn)證:確保Class文件中的字節(jié)流中包含信息符合當(dāng)前虛擬機(jī)要求,保證被加載類的正確性,不會危害虛擬機(jī)。
主要完成4個檢驗(yàn)動作:(1)文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,能被當(dāng)前版本虛擬機(jī)處理。主要目的是保證輸入的字節(jié)流能正確的解析并存儲在方法區(qū)上,通過驗(yàn)證后,字節(jié)流才能進(jìn)入內(nèi)存的方法區(qū)進(jìn)行存儲,后面的是那個階段全部是基于方法區(qū)的存儲結(jié)構(gòu)進(jìn)行的,不會再直接操作字節(jié)流了。(2)元數(shù)據(jù)驗(yàn)證:對類的元數(shù)據(jù)信息進(jìn)行語義校驗(yàn),保證描述的信息符合java語言規(guī)范。(3)字節(jié)碼驗(yàn)證:第二階段對元數(shù)據(jù)信息中的數(shù)據(jù)類型做完校驗(yàn)后,接著就是對類的方法體進(jìn)行校驗(yàn)分析,保證被校驗(yàn)類的方法在運(yùn)行時不會危害虛擬機(jī)。(4)符號引用校驗(yàn):發(fā)生在將符號引用轉(zhuǎn)化成直接引用的時候,也就是解析階段。注意:驗(yàn)證階段是非常重要的,但不是必要的,如果確保代碼沒問題,可以通過設(shè)置參數(shù)-Xverify:none參數(shù)來關(guān)閉大部分驗(yàn)證措施,來縮短虛擬機(jī)類加載時間。 - 準(zhǔn)備:為類變量分配內(nèi)存并設(shè)置初始值,基本類型的初始值為0,也能用類型的初始值為null。這些變量所使用的內(nèi)存都將在方法區(qū)進(jìn)行分配,另外這里分配的類變量,而不是實(shí)例變量,實(shí)例變量是在對象實(shí)例化后隨對象一起分配到j(luò)ava堆內(nèi)存中。通常情況下初始值為0,還有特殊情況,在類字段的字段屬性中存在ContantValue屬性的話,在準(zhǔn)備階段變量的值就會被初始化成指定的值。static final修飾的字段就會在編譯期生成ContantValue屬性,在類加載的準(zhǔn)備階段直接把constantValue的值賦給該字段。
- 解析:將常量池內(nèi)的符號引用替換成直接引用的過程。符號引用:是任意形式的字面量,用一組符號來描述引用的目標(biāo),只要無歧義的定位到目標(biāo)即可,與內(nèi)存布局無關(guān)。直接引用:是直接指向模具表的指針,相對偏移量或是一個能間接定位到目標(biāo)的句柄,與內(nèi)存布局有關(guān)。
- 初始化:執(zhí)行類構(gòu)造器<clinit>()方法的過程,對static修飾的變量進(jìn)行初始化,沒有類變量的情況下。<init>是對象構(gòu)造器,對非靜態(tài)變量解析初始化,而<clinit>是類構(gòu)造器對靜態(tài)變量,對靜態(tài)代碼塊進(jìn)行初始化。
示例:
public class Test {
private int num_1 = 11;
private static int num_2 = 22;
private static final int num_3 = 33;
private int num_4;
public Test(){
this.num_4 = 44;
}
}

反編譯字節(jié)碼.png
類加載器分類

類加載器
- 引導(dǎo)類加載器(啟動類加載器,Bootstrap ClassLoader)
(1)使用c/c++實(shí)現(xiàn),嵌套于jvm內(nèi)部,
(2)用來加載java核心類庫,提供jvm自身需要的類(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路徑下的內(nèi)容)
(3)不繼承java.lang.ClassLoader,沒有父加載器
(4)加載擴(kuò)展類和應(yīng)用程序加載器,并指定為他們的父類加載器
(5)Bootstrap加載器只加載包名為java、javax、sun等開頭的類 - 擴(kuò)展類加載器(Extension ClassLoader)
(1)java語言編寫,由sun.misc.Launcher$extClassLoader實(shí)現(xiàn)
(2)派生于ClassLoader抽象類
(3)父類加載器為引導(dǎo)類加載器
(4)從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類庫,或從JDK的安裝目錄的jre/lib/ext子目錄下加載類庫,如果用戶創(chuàng)建的jar放在此目錄,也會自動由擴(kuò)展類加載器加載。 - 應(yīng)用程序類加載器(系統(tǒng)類加載器 AppClassLoader)
(1)java語言編寫,由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)
(2)派生于ClassLoader抽象類
(3)負(fù)責(zé)加載環(huán)境變量classpath或系統(tǒng)屬性java.class.path指定路徑下的類庫
(4)是程序默認(rèn)的類加載器,一般來說,java應(yīng)用都是由應(yīng)用程序類加載器來加載
//類加載器
public class ClassLoaderTest {
public static void main(String[] args) {
//獲取系統(tǒng)類加載器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//獲取其上層:擴(kuò)展類加載器
ClassLoader extClassLoader = systemClassLoader.getParent();
System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d
//獲取其上層:獲取不到引導(dǎo)類加載器
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader);//null
//對于用戶自定義類來說:默認(rèn)使用系統(tǒng)類加載器進(jìn)行加載
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//String類使用引導(dǎo)類加載器進(jìn)行加載的。---> Java的核心類庫都是使用引導(dǎo)類加載器進(jìn)行加載的。
ClassLoader classLoader1 = String.class.getClassLoader();
System.out.println(classLoader1);//null
}
}
輸出結(jié)果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@61bbe9ba
null
sun.misc.Launcher$AppClassLoader@18b4aac2
null
Process finished with exit code 0
//類加載器的加載路徑
public class ClassLoaderTestPath {
public static void main(String[] args) {
System.out.println("**********啟動類加載器**************");
//獲取BootstrapClassLoader能夠加載的api的路徑
URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL element : urLs) {
System.out.println(element.toExternalForm());
}
//從上面的路徑中隨意選擇一個類,來看看他的類加載器是什么:引導(dǎo)類加載器
ClassLoader classLoader = Provider.class.getClassLoader();
System.out.println(classLoader);
System.out.println("***********擴(kuò)展類加載器*************");
String extDirs = System.getProperty("java.ext.dirs");
for (String path : extDirs.split(";")) {
System.out.println(path);
}
System.out.println("***********應(yīng)用程序類加載器*************");
//從上面的路徑中隨意選擇一個類,來看看他的類加載器是什么:擴(kuò)展類加載器
ClassLoader classLoader1 = CurveDB.class.getClassLoader();
System.out.println(classLoader1);//sun.misc.Launcher$ExtClassLoader@1540e19d
}
}
輸出結(jié)果:
**********啟動類加載器**************
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/classes
null
***********擴(kuò)展類加載器*************
/Users/cuiqingdong/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
***********應(yīng)用程序類加載器*************
sun.misc.Launcher$ExtClassLoader@355da254
雙親委派機(jī)制

雙親委派機(jī)制
工作原理:
(1)如果一個類加載器收到類加載的請求,它并不會自己先去加載,而是將這個請求委托給父類的加載器去執(zhí)行
(2)如果父類加載器還存在父類加載器,則進(jìn)一步向上委托,依次遞歸,請求最終到達(dá)頂層的引導(dǎo)類加載器
(3)如果父類加載器可以完成類加載任務(wù),就成功返回,如果父類加載器無法完成加載任務(wù),子加載器才會嘗試自己去加載,這就是雙親委托機(jī)制。
優(yōu)勢:
(1)避免類的重復(fù)加載
(2)防止核心api被隨意篡改
沙箱安全機(jī)制
自定義String類,但是在加載自定義String類的時候會率先使用引導(dǎo)類加載器加載,而引導(dǎo)類加載器在加載的過程中會先加載jdk自帶的文件(rt.jar包中java/lang/String.class),報錯信息說沒有main方法,就是因?yàn)榧虞d的是rt.jar包中的String類,這樣保證對java核心源代碼的保護(hù),就是沙箱安全機(jī)制。