概述
虛擬機把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存中,并對數(shù)據(jù)進行驗證请祖,準備订歪,解析,初始化的一個過程肆捕,最終是可以被虛擬機直接使用的java類型刷晋,這就是類加載的一個簡單的過程。
Java中的類加載是在運行時加載,這樣會比較的消耗性能眼虱,但是正是在運行時加載使得java擁有很好的靈活性和可擴展性喻奥。
類加載的時機
類從被加載到內(nèi)存中開始,到卸載出內(nèi)存為止捏悬。它的生命周期總共七個階段:加載---->驗證---->準備---->解析---->初始化---->使用---->卸載撞蚕。其中解析這個過程是不確定的,它可能會在初始化后之后过牙,這是為了使java支持運行時的綁定甥厦。
- new ,getstatic寇钉,putstatic刀疙,invokestatic這四條指令時會觸發(fā)初始化的操作。
- new是new一個新的對象時會觸發(fā)初始化摧莽。
- getstatic是獲取靜態(tài)字段時會觸發(fā)庙洼。
- putstatic是設(shè)置靜態(tài)字段時會觸發(fā)。
- invokestatic是調(diào)用另一個類的靜態(tài)方法的時候镊辕。
PS:需要注意的是getstatic和putstatic被final修飾的油够,在編譯期就放入到常量池中是不會觸發(fā)的。 - 使用java.lang.reflect的包方法對類進行反射調(diào)用時征懈,如果類沒有初始化就需要進行初始的操作石咬。
- 子類進行初始化時需要對父類先進行初始。
- java啟動時需要的啟動主類卖哎,程序的入口鬼悠。該類就需要進行初始化。
- 使用JDK1.7的動態(tài)語言支持時亏娜,如果一個java.lang.invoke.Methondhandle實例最后解析結(jié)果REF_getStatic焕窝,REF_putStatic,REF_invokeStatic的方法句柄维贺,如果沒有進行初始化時會觸發(fā)初始化它掂。
PS:接口的初始化和類初始化不同,接口初始化只和類初始化的子類初始化是需要父類先進行初始化溯泣,而且并不是接口父類中的所有都是會初始化虐秋。
加載
加載是類加載中前面提到的其中的一個過程。類加載的基本過程:
- 通過全限定類名加載二進制流垃沦。
- 將二進制流代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)換方法區(qū)中運行時的數(shù)據(jù)結(jié)構(gòu)客给。
- 在內(nèi)存中生成java.lang.Class對象,將這個作為該方法區(qū)這個類中各種數(shù)據(jù)的一個入口肢簿。
加載分為數(shù)組類加載過程和非數(shù)組類的加載過程靶剑。java的數(shù)組類的加載過程其實是有虛擬機直接加載的但是數(shù)組中的類型需要類加載機制加載:
- 非數(shù)組類加載機制:可控性強既可以有系統(tǒng)類加載器進行加載又可以由用戶自定義的類加載器進行加載蜻拨。(重寫一個類加載器的loadClass()方法)。
- 數(shù)組類型的加載機制:數(shù)組類型的加載機制如果是引用類型抬虽,就使用遞歸進行加載官觅,并且會在加載的類型上加入一個標志。如果是非引用類型則會把標志與引導(dǎo)類加載器關(guān)聯(lián)阐污。
ps:數(shù)組類的可見性與它組件的可見性是相同的休涤,如果組件類型不是引用類型的可見性一般設(shè)置為public。
類加載完成會有一個連接笛辟,可能在沒完成加載就開始連接功氨,雖然如此但是該順序是一定的。
驗證
驗證的主要目的是保證加載進來的Class文件的字節(jié)流包含的信息符合虛擬機的當(dāng)前的要求手幢,不會有危害自身的數(shù)據(jù)存在捷凄。
Java是相對C++語言是安全的語言,例如它有C++不具有的數(shù)組越界的檢查围来。這本身就是對自身安全的一一種保護跺涤。驗證階段是Java非常重要的一個階段,它會直接的保證應(yīng)用是否會被惡意入侵的一道重要的防線监透,越是嚴謹?shù)尿炞C機制越安全桶错。驗證的四個階段文件格式驗證-->元數(shù)據(jù)驗證-->字節(jié)碼驗證-->符號引用驗證。
- 文件格式驗證:主要驗證字節(jié)流是否符合Class文件格式規(guī)范胀蛮,并且能被當(dāng)前的虛擬機加載處理院刁。
- 是否以魔數(shù)開頭。
- 主粪狼,次版本號是否在當(dāng)前虛擬機處理的范圍之內(nèi)退腥。
- 常量池中是否有不被支持的常量類型。
- 指向常量的中的索引值是否存在不存在的常量或不符合類型的常量再榄。
- CONSTANT_Utf8_info型的常量中有不符合utf8格式的編碼數(shù)據(jù)狡刘。
還有大其它的驗證這里就不一一的列舉。 - 元數(shù)據(jù)驗證:對字節(jié)碼描述的信息進行語義的分析困鸥,分析是否符合java的語言語法的規(guī)范嗅蔬。
- 字節(jié)碼驗證:最重要的驗證環(huán)節(jié),分析數(shù)據(jù)流和控制窝革,確定語義是合法的郑口,符合邏輯的似袁。主要的針對元數(shù)據(jù)驗證后對方法體的驗證吞歼。保證類方法在運行時不會有危害出現(xiàn)扶关。
- 符號引用驗證:主要是針對符號引用轉(zhuǎn)換為直接引用的時候佩微,是會延伸到第三解析階段削饵,主要去確定訪問類型等涉及到引用的情況加叁,主要是要保證引用一定會被訪問到扮休,不會出現(xiàn)類等無法訪問的問題。
雖然驗證很重要但是并不是必須的階段厢拭。當(dāng)然大量重復(fù)的驗證會相當(dāng)?shù)幕ㄙM性能和時間的兰英。
準備
準備階段主要是類變量進行分配內(nèi)存和數(shù)據(jù)的初始化階段,所謂的初始化并不是你編碼時所定義的變量值供鸠。例如:
public static int age = 20;
數(shù)據(jù)的初始化并不會將它初始化為20畦贸,而是初始化為0,系統(tǒng)有一套自己的初始化值楞捂。如下圖:
數(shù)據(jù)類型 | 零值 |
---|---|
int | 0 |
long | 0L |
short | 0 |
char | '\u0000' |
byte | 0 |
boolean | false |
float | 0.0f |
double | 0.0d |
reference | null |
當(dāng)然會有特殊的情況薄坏,如下面的代碼:
public static final int value = 20;
這種情況是類的字段時存在ConstantValue屬性所指定的字段。用final修飾后出現(xiàn)該屬性寨闹,加初始化時會直接的使用ConstantValue的屬性值胶坠,所以會初始化為20。
解析
解析是將常量池中的符號引用轉(zhuǎn)化為直接引用的過程繁堡,還記得前面驗證階段時出現(xiàn)的符號引用驗證嗎沈善?就是對該階段的驗證。
- 符號引用:符號引用是以一組符號來描述所引用的目標椭蹄,符號可以是任何的字面形式的字面量闻牡,只要不會出現(xiàn)沖突能夠定位到就行。布局和內(nèi)存無關(guān)塑娇。
- 直接引用:是指向目標的指針澈侠,偏移量或者能夠直接定位的句柄。該引用是和內(nèi)存中的而布局有關(guān)的埋酬,并且一定加載進來的哨啃。
虛擬機可能會多次的進行解析。解析主要的對類写妥,接口拳球,字段,類方法珍特,接口方法祝峻,方法類型,方法句柄和調(diào)用點限定符引用進行扎筒。這七種解析有細節(jié)上的不同莱找,主要的思想是通過限定性類名找到解析的類型進行解析。主要的是會分為數(shù)組類型嗜桌,非數(shù)組類型存在一個直接進行解析的過程奥溺。在過程還有從下上的匹配查找(主要出現(xiàn)在有繼承,接口的情況下)骨宠。
初始化
初始化算是類加載過程的最后一個階段浮定,在這個階段在是真正的開始有java代碼主導(dǎo)相满。大家應(yīng)該記得在準備階段已經(jīng)進行過一次賦值,但是只是系統(tǒng)的默認賦值(ConstantValue的例外情況)桦卒。初始化是執(zhí)行<clinit>的過程立美。
- <clinit>的主要是查找static模塊,用戶自定義類變量的賦值方灾,該順序是由文件中的順序界定的建蹄。加載過程存在的是父類的一定會比子類先進行加載到,因為會保證子類的<clinit>加載完成時父類的<clinit>一定會加載完成裕偿。所有就像大家所知道的java.lang.object一定會是虛擬機中第一個加載完成的躲撰。
- <clinit>在接口中的加載是不同的它是不存在靜態(tài)塊的,接口中也是會有賦值進行的击费,但是接口中的是在需要用到才會去進行加載的拢蛋。
- 允許在定義之前進行賦值的操作,但是不允許使用蔫巩,如下:
public class A{
static{
s = 20;
//system.out.printf(s);
上面注釋的這句話時會出現(xiàn)錯誤的谆棱;
}
static int s = 10;
}
- 虛擬機會保證在多線程的環(huán)境下進行加鎖,保證正確執(zhí)行圆仔。如果有多個進行加載一個會保證只有一個去加載垃瞧,其他的會進去阻塞等待中。同一個類只會加載一次坪郭,就算多個進入阻塞也不會重新喚醒个从。
類加載器
- 類與類加載器:一個類的相同判斷條件大家都知道,但是如果不是由同一個類加載器加載出來的歪沃,就算是看起來相同的也是出現(xiàn)false的嗦锐。
- 三大類加載器:
- 啟動類加載器
- 擴展類加載器
- 應(yīng)用程序類加載器
-
雙親委托機制:
雙親委托機制是當(dāng)一個類進入加載時,子加載器不會自己嘗試去加載沪曙,而是將其發(fā)送到它的父加載器中加載奕污,以此類推直到達到最后的加載器,只有當(dāng)父加載器不能進行加載是會發(fā)送到子加載器中液走,子加載此時才會嘗試去加載碳默。
public Class<?> loadClass(String name)throws ClassNotFoundException {
return loadClass(name, false);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
// 首先判斷該類型是否已經(jīng)被加載
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
//如果不存在父類加載器,就檢查是否是由啟動類加載器加載的類缘眶,通過調(diào)用本地方法native Class findBootstrapClass(String name)
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 如果父類加載器和啟動類加載器都不能完成加載任務(wù)嘱根,才調(diào)用自身的加載功能
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
- 雙親委托機制的破壞
- 1.2版本為了向前兼容1.0版本
- 本身模型的問題,基礎(chǔ)類要調(diào)用用戶類而出現(xiàn)的沖突巷懈。通過設(shè)置線程上下文類加載器该抒,如果出現(xiàn)上面這種情況,通過上下文類加載器去加載所需的類砸喻。
- 用戶對動態(tài)性的追求柔逼,出現(xiàn)沒一個模塊都有自己的類加載器,如果需要更換時連同類加載器一同換掉割岛。