一 類加載過(guò)程
類加載:類加載器將class文件加載到j(luò)vm內(nèi)存中
(1) 加載:從磁盤查找并在找到后通過(guò)IO讀入字節(jié)碼文件
(2)建立連接做如下工作:
驗(yàn)證:驗(yàn)證字節(jié)碼文件內(nèi)容是否正確,有相應(yīng)的語(yǔ)法邏輯
準(zhǔn)備:給類的靜態(tài)變量分配內(nèi)存,并賦予java虛擬機(jī)規(guī)定的默認(rèn)值仗颈,比如 static int a=5丐巫,會(huì)賦予默認(rèn)值int a=0
解析:類加載器將類所引用的其他類裝載
(3)初始化:對(duì)類的靜態(tài)變量初始化為用戶代碼中指定的值,比如static int a=5知允,在這一步賦予值5
二 類加載器分類
JVM角度:
- 啟動(dòng)類加載器(bootstrap ClassLoader),該加載器使用C++實(shí)現(xiàn),是虛擬機(jī)自身一部分
- 其他所有的類加載器忽肛,這些加載器都由java實(shí)現(xiàn),獨(dú)立于JVM烂斋,全部繼承自java.lang.ClassLoader
開(kāi)發(fā)人員角度:
- 啟動(dòng)類加載器(Bootstrap ClassLoader)
- 擴(kuò)展類加載器(Extension ClassLoader)
- 應(yīng)用程序類加載器(Application ClassLoader)
應(yīng)用程序由著三種類加載器互相配合完成加載屹逛,三者關(guān)系如下圖
圖中的自定義加載器在我們自己寫程序的時(shí)候基本不會(huì)用到础废,一般如tomcat之類的中間件會(huì)使用到自定義類加載器
我們通過(guò)程序來(lái)看一下這幾種加載器
public class Test2 {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(Test2.class.getClassLoader().getClass().getName());
// 系統(tǒng)類加載器就是應(yīng)用程序類加載器,通過(guò)上面一行代碼和下面一行代碼均可以打印
System.out.println(ClassLoader.getSystemClassLoader().getClass().getName());
}
}
之所以第一個(gè)string的類加載器沒(méi)有打印結(jié)果是因?yàn)榧逶矗瑂tring屬于rt.jar色迂,而rt.jar中含有jdk中最核心的類,是c和c++語(yǔ)言寫的手销,因此根本就不會(huì)知道他的名字
三 類加載機(jī)制
3.1 全盤負(fù)責(zé)委托機(jī)制
當(dāng)一個(gè)加載器加載了類A歇僧,則類A所依賴和引用的類比如類B和類C都將由加載類A的加載器加載
3.2 雙親委派機(jī)制
上圖中類加載器之間的層次關(guān)系,就叫做類加載器的雙親委派模型(parents delegation model)锋拖。雙親委派機(jī)制于jdk1.2引入诈悍。特點(diǎn)是除了頂層的啟動(dòng)類加載器外,其他的類加載器都應(yīng)當(dāng)有自己的父類加載器
父類加載器不是說(shuō)子類加載器繼承了父類加載器兽埃,而是子類加載器持有父類加載器的引用侥钳,可以通過(guò)這個(gè)引用找到父類加載器
3.2.1 雙親委派機(jī)制加載過(guò)程
如果一個(gè)類加載器收到類加載的請(qǐng)求,他首先不會(huì)去自己嘗試加載這個(gè)類柄错,而是將這個(gè)請(qǐng)求委派給自己的父類加載器完成舷夺,每個(gè)層次的類加載器都是這樣,因此所有的加載請(qǐng)求最后都會(huì)傳送給頂層的啟動(dòng)類加載器售貌,只有當(dāng)父類加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求(他的搜索范圍沒(méi)有找到所需的類)時(shí)候给猾,子加載器才會(huì)嘗試去加載
比如加載我們自己寫的類A.class,類A中引用了我們自己寫的java.lang.string.class颂跨,則應(yīng)用程序加載器在加載類A的時(shí)候敢伸,會(huì)順便嘗試加載我們自己寫的string.class,加載的話不是直接加載而是委托給父類加載器一級(jí)一級(jí)上去直到啟動(dòng)類加載器加載到正確的string.class
測(cè)試案例:
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("hello world");
}
}
可以看到程序沒(méi)有打印這句話恒削,因?yàn)椴](méi)有加載我們自己的string.class而是加載了jdk自己的string.class池颈,實(shí)際運(yùn)行的是jdk的string類,jdk的string類是沒(méi)有main方法的因此報(bào)錯(cuò)
3.2.2 雙親委派機(jī)制優(yōu)點(diǎn)
- 沙箱安全機(jī)制:
自己寫的java.lang.string.class類不會(huì)被加載钓丰,這樣可以防止jdk核心rt.jar中類被隨意篡改
因?yàn)榧?/li> - 避免類的重復(fù)加載
當(dāng)父類已經(jīng)加載了該類以后躯砰,子類加載器將不再加載該類
四 JVM加載jar包是否將jar包中所有類加載到內(nèi)存
jvm對(duì)class文件的加載是按需加載,不會(huì)一次性加載
代碼示例:
下面代碼執(zhí)行的時(shí)候携丁,需要在執(zhí)行前添加執(zhí)行參數(shù): -verbose:class 弃揽,加上以后除了打印正常輸出之外,還會(huì)打印jvm中類加載相關(guān)的信息
public class TestDynamicLoad {
static {
System.out.println("*************static code************");
}
public static void main(String[] args){
new A();
System.out.println("*************load test************");
new B();
}
}
class A{
public A(){
System.out.println("*************initial A************");
}
}
class B{
public B(){
System.out.println("*************initial B************");
}
}
從上面的運(yùn)行結(jié)果可以看到则北,由之前學(xué)到的靜態(tài)代碼塊是在類初始化的時(shí)候執(zhí)行的矿微,這個(gè)時(shí)候已經(jīng)完成了類加載,因此我們?cè)谇懊婵梢钥吹疆?dāng)前類被加載的信息尚揣,但是當(dāng)前類應(yīng)用的類A和類B并未在開(kāi)始的時(shí)候就加載涌矢,而是在用到了以后才加載