類加載過程
類從被加載到虛擬機(jī)內(nèi)存開始,直到被卸載出內(nèi)存為止,它的整個(gè)生命周期過程是:
加載 ---> 驗(yàn)證 ---> 準(zhǔn)備 ---> 解析 ---> 初始化 ---> 使用 ---> 卸載
加載
加載是類加載過程的第一個(gè)階段,在加載階段虛擬機(jī)需要完成三件事。
- 通過一個(gè)類的全限定名來獲取其定義的二進(jìn)制字節(jié)流
- 將這個(gè)字節(jié)流所表示的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
- 在內(nèi)存中生成一個(gè)代表這個(gè)類的class對象,作為方法區(qū)的各種數(shù)據(jù)的訪問入口
類加載器
類的加載的全都是交給類加載器去實(shí)現(xiàn)的。
在Java虛擬機(jī)規(guī)范中,加載器是分為兩大類:
-
引導(dǎo)類加載器
引導(dǎo)類加載器是使用本地代碼實(shí)現(xiàn)的類加載器,負(fù)責(zé)將<JAVA_HOME>/lib下的核心類庫或-Xbootclasspath選項(xiàng)指定的jar包給加載到內(nèi)存中。
這個(gè)加載器是屬于虛擬機(jī)部分的類,Java中是無法獲取到的
-
自定義加載器
自定義加載器可以分為三類:
-
擴(kuò)展類加載器
擴(kuò)展類加載器是負(fù)責(zé)將<JAVA_HOME>/lib/ext或者由系統(tǒng)變量-Djava.ext.dir指定位置中的類庫加載到內(nèi)存中
-
應(yīng)用程序類加載器
應(yīng)用程序類加載器是將classPath中所指定的類,一般來說Java類都是由這個(gè)類加載器來加載的
-
自定義類加載器
除了系統(tǒng)提供的類之外,也可以通過繼承java.lang.ClassLoader類的方式實(shí)現(xiàn)自己的類加載器
-
類的加載順序 ----- 雙親委派模型
他們之間的關(guān)系如下圖所示:
JVM加載類的時(shí)候默認(rèn)采用的是雙親委派機(jī)制,
也是是說在某個(gè)類加載器在接收到加載類的請求的時(shí)候,
首先是將加載任務(wù)委托給父類加載器,依次遞歸,如果父類加載器能夠完成加載就成功返回,
只有當(dāng)父類加載器無法完成加載任務(wù)時(shí)候,才會(huì)去調(diào)用自己去加載類。
在JDK中有默認(rèn)的擴(kuò)展類加載器和應(yīng)用程序類加載器
sun.misc.Launcher$ExtClassLoader 和 sun.misc.Launcher$AppClassLoader
可以用代碼看一下他們的加載關(guān)系:
public class Main {
public static void main(String[] args) {
System.out.println(ClassLoader.getSystemClassLoader());
System.out.println(ClassLoader.getSystemClassLoader().getParent());
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
}
}
運(yùn)行結(jié)果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@60e53b93
null
這個(gè)運(yùn)行結(jié)果能夠驗(yàn)證我們之前雙親委派模型的圖,應(yīng)用程序類加載器調(diào)用擴(kuò)展類加載器最后調(diào)用一個(gè)訪問不到的引導(dǎo)類加載器
他們的依賴關(guān)系:
<figure class="half"> <img src="http://ztianzeng.com/images/Java的類加載機(jī)制/yilaiguanxi.jpg" alt="圖片說明" > <img src="http://ztianzeng.com/images/Java的類加載機(jī)制/yilaiguanxi2.jpg" alt="圖片說明" > </figure>
他們最后都是調(diào)用ClassLoader這個(gè)里面的loadClass實(shí)現(xiàn)類加載
雙親委派模型的類加載邏輯:
- 首先檢查c有沒有被加載
- 如果沒有沒加載,查看父類加載器是否為空,為空調(diào)用父類加載器加載,否則調(diào)用引導(dǎo)類加載器加載
- 如果c在父類加載器加載下沒有加載成功,會(huì)調(diào)用當(dāng)前加載器的findClass進(jìn)行加載
- 若class最終被加載,且resolve為true,則對該class進(jìn)行鏈接操作
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
驗(yàn)證
驗(yàn)證階段用于確保類或接口的二進(jìn)制結(jié)構(gòu)上是正確的。需要滿足Java虛擬機(jī)限制中描述的靜態(tài)或者結(jié)構(gòu)上的約束
準(zhǔn)備
準(zhǔn)備階段的任務(wù)是為類或接口的靜態(tài)字段分配空間,并用初始值初始化這些字段,這個(gè)階段不會(huì)執(zhí)行任何的虛擬機(jī)字節(jié)碼指令。
也是就說不會(huì)初始化任何實(shí)例變量。
初始值是指Java虛擬機(jī)規(guī)范所支持的數(shù)據(jù)類型和對應(yīng)的初始值。
比如:
public static int value = 2;
它最準(zhǔn)備階段被賦予的初始值是0而不是2。
在類的對象創(chuàng)建之后的任何時(shí)間,都可以進(jìn)行準(zhǔn)備階段,但它得確保一定要在初始化階段開始前完成。
解析
解析是根據(jù)運(yùn)行時(shí)常量池的符號(hào)引用來動(dòng)態(tài)決定具體的值的過程。
總共要解析六種類型:
類與接口解析
字段解析
普通方法解析
接口方法解析
方法類型與方法句柄解析
調(diào)用點(diǎn)限定符解析
初始化
初始化過程才是虛擬機(jī)真正執(zhí)行Java代碼的過程。
會(huì)觸發(fā)初始化過程的有這幾種行為:
- 在被選為Java啟動(dòng)的初始類
- 在對于某個(gè)類的子類初始化時(shí)
- 在調(diào)用JDK類庫的反射方法時(shí)
- 在調(diào)用java.lang.invoke.MethodHandle時(shí),如果檢測出來有static方法時(shí)
- 在執(zhí)行new方法、初始代碼塊或靜態(tài)代碼塊時(shí)
在多線程環(huán)境下,虛擬機(jī)會(huì)保證一個(gè)類的初始化方法會(huì)被正確的加鎖和同步。
使用
在初始化完成之后才能夠?qū)︻愡M(jìn)行調(diào)用
卸載
卸載時(shí)Java虛擬機(jī)將類移出內(nèi)存的過程。