類加載過程
類從被加載到虛擬機內存開始器紧,直到被卸載出內存為止,它的整個生命周期過程是:
加載 ---> 驗證 ---> 準備 ---> 解析 ---> 初始化 ---> 使用 ---> 卸載
加載
加載是類加載過程的第一個階段楼眷,在加載階段虛擬機需要完成三件事铲汪。
- 通過一個類的全限定名來獲取其定義的二進制字節(jié)流
- 將這個字節(jié)流所表示的靜態(tài)存儲結構轉化為方法區(qū)運行時數據結構
- 在內存中生成一個代表這個類的class對象熊尉,作為方法區(qū)的各種數據的訪問入口
類加載器
類的加載的全都是交給類加載器去實現的。
在Java虛擬機規(guī)范中掌腰,加載器是分為兩大類:
-
引導類加載器
引導類加載器是使用本地代碼實現的類加載器狰住,負責將<JAVA_HOME>/lib下的核心類庫或-Xbootclasspath選項指定的jar包給加載到內存中。
這個加載器是屬于虛擬機部分的類齿梁,Java中是無法獲取到的
-
自定義加載器
自定義加載器可以分為三類:
-
擴展類加載器
擴展類加載器是負責將<JAVA_HOME>/lib/ext或者由系統(tǒng)變量-Djava.ext.dir指定位置中的類庫加載到內存中
-
應用程序類加載器
應用程序類加載器是將classPath中所指定的類催植,一般來說Java類都是由這個類加載器來加載的
-
自定義類加載器
除了系統(tǒng)提供的類之外,也可以通過繼承java.lang.ClassLoader類的方式實現自己的類加載器
-
類的加載順序 ----- 雙親委派模型
他們之間的關系如下圖所示:
JVM加載類的時候默認采用的是雙親委派機制士飒,
也是是說在某個類加載器在接收到加載類的請求的時候,
首先是將加載任務委托給父類加載器蔗崎,依次遞歸酵幕,如果父類加載器能夠完成加載就成功返回,
只有當父類加載器無法完成加載任務時候缓苛,才會去調用自己去加載類芳撒。
在JDK中有默認的擴展類加載器和應用程序類加載器
sun.misc.Launcher$ExtClassLoader 和 sun.misc.Launcher$AppClassLoader
可以用代碼看一下他們的加載關系:
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());
}
}
運行結果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@60e53b93
null
這個運行結果能夠驗證我們之前雙親委派模型的圖,應用程序類加載器調用擴展類加載器最后調用一個訪問不到的引導類加載器
他們的依賴關系:
<figure class="half"> <img src="http://ztianzeng.com/images/Java的類加載機制/yilaiguanxi.jpg" alt="圖片說明" > <img src="http://ztianzeng.com/images/Java的類加載機制/yilaiguanxi2.jpg" alt="圖片說明" > </figure>
他們最后都是調用ClassLoader這個里面的loadClass實現類加載
雙親委派模型的類加載邏輯:
- 首先檢查c有沒有被加載
- 如果沒有沒加載未桥,查看父類加載器是否為空笔刹,為空調用父類加載器加載,否則調用引導類加載器加載
- 如果c在父類加載器加載下沒有加載成功冬耿,會調用當前加載器的findClass進行加載
- 若class最終被加載舌菜,且resolve為true,則對該class進行鏈接操作
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;
}
}
驗證
驗證階段用于確保類或接口的二進制結構上是正確的亦镶。需要滿足Java虛擬機限制中描述的靜態(tài)或者結構上的約束
準備
準備階段的任務是為類或接口的靜態(tài)字段分配空間日月,并用初始值初始化這些字段,這個階段不會執(zhí)行任何的虛擬機字節(jié)碼指令缤骨。
也是就說不會初始化任何實例變量爱咬。
初始值是指Java虛擬機規(guī)范所支持的數據類型和對應的初始值。
比如:
public static int value = 2绊起;
它最準備階段被賦予的初始值是0而不是2精拟。
在類的對象創(chuàng)建之后的任何時間,都可以進行準備階段虱歪,但它得確保一定要在初始化階段開始前完成蜂绎。
解析
解析是根據運行時常量池的符號引用來動態(tài)決定具體的值的過程。
總共要解析六種類型:
類與接口解析
字段解析
普通方法解析
接口方法解析
方法類型與方法句柄解析
調用點限定符解析
初始化
初始化過程才是虛擬機真正執(zhí)行Java代碼的過程笋鄙。
會觸發(fā)初始化過程的有這幾種行為:
- 在被選為Java啟動的初始類
- 在對于某個類的子類初始化時
- 在調用JDK類庫的反射方法時
- 在調用java.lang.invoke.MethodHandle時荡碾,如果檢測出來有static方法時
- 在執(zhí)行new方法、初始代碼塊或靜態(tài)代碼塊時
在多線程環(huán)境下局装,虛擬機會保證一個類的初始化方法會被正確的加鎖和同步坛吁。
使用
在初始化完成之后才能夠對類進行調用
卸載
卸載時Java虛擬機將類移出內存的過程劳殖。