From:深入理解Java虛擬機(jī)
- 目錄
BiBi - JVM -0- 開(kāi)篇
BiBi - JVM -1- Java內(nèi)存區(qū)域
BiBi - JVM -2- 對(duì)象
BiBi - JVM -3- 垃圾收集算法
BiBi - JVM -4- HotSpot JVM
BiBi - JVM -5- 垃圾回收器
BiBi - JVM -6- 回收策略
BiBi - JVM -7- Java類(lèi)文件結(jié)構(gòu)
BiBi - JVM -8- 類(lèi)加載機(jī)制
BiBi - JVM -9- 類(lèi)加載器
BiBi - JVM -10- 虛擬機(jī)字節(jié)碼
BiBi - JVM -11- 編譯期優(yōu)化
BiBi - JVM -12- 運(yùn)行期優(yōu)化
BiBi - JVM -13- 并發(fā)
Java天生的動(dòng)態(tài)擴(kuò)展語(yǔ)言特性是依賴(lài)運(yùn)行期動(dòng)態(tài)加載和動(dòng)態(tài)連接這個(gè)特點(diǎn)實(shí)現(xiàn)的挡鞍。
例如:在編寫(xiě)一個(gè)面向接口的應(yīng)用程序,可以等到運(yùn)行時(shí)再指定其實(shí)際的實(shí)現(xiàn)類(lèi),用戶可以通過(guò)Java預(yù)定義的或自定義的類(lèi)加載器,讓一個(gè)本地的應(yīng)用程序可以在運(yùn)行時(shí)從網(wǎng)絡(luò)或其它地方加載一個(gè)二進(jìn)制流作為程序代碼的一部分讼育。
類(lèi)的生命周期
1)加載
2)連接:驗(yàn)證 -> 準(zhǔn)備 -> 解析
3)初始化
4)使用
5)卸載
其中【解析】階段在某些情況下可以在【初始化】階段之后再開(kāi)始峡竣,這是為了支持Java語(yǔ)言的運(yùn)行時(shí)綁定【動(dòng)態(tài)綁定】租谈。
有且只有5種情況必須立即進(jìn)行【初始化】
1)遇到new偷厦、getstatic、putstatic诀紊、invokestatic谒出,對(duì)應(yīng)的場(chǎng)景:使用new關(guān)鍵字實(shí)例化對(duì)象、讀取或設(shè)置一個(gè)類(lèi)的靜態(tài)字段【非final】邻奠、調(diào)用一個(gè)類(lèi)的靜態(tài)方法笤喳。
2)使用反射對(duì)類(lèi)進(jìn)行調(diào)用。
3)當(dāng)一個(gè)類(lèi)初始化時(shí)碌宴,先初始化其父類(lèi)杀狡。
4)當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(lèi)【包含main方法】贰镣,虛擬機(jī)會(huì)先初始化這個(gè)主類(lèi)呜象。
5)使用JDK1.7的動(dòng)態(tài)語(yǔ)言支持時(shí)膳凝,如果一個(gè)MethodHandler實(shí)例最后的解析結(jié)果是REF_getStatic、REF_putStatic恭陡、REF_invokeStatic的方法句柄蹬音。
- 例子:
public class A {
public static int value = 20;
public static final String name = "ljg";
}
public class B extends A {
}
通過(guò)子類(lèi)引用父類(lèi)中的靜態(tài)字段,不會(huì)初始化子類(lèi)休玩。
即:B.value=33; 會(huì)實(shí)例化父類(lèi)A著淆,而不會(huì)實(shí)例化類(lèi)B。
對(duì)于靜態(tài)字段拴疤,只有直接定義這個(gè)字段的類(lèi)才會(huì)被初始化永部。
訪問(wèn)被final修飾的靜態(tài)字段,不會(huì)初始化該類(lèi)呐矾。
即:訪問(wèn)A.name時(shí)苔埋,不會(huì)實(shí)例化類(lèi)A。因?yàn)椋罕籪inal修飾蜒犯,已經(jīng)在編譯期把結(jié)果放入到常量池了组橄。
數(shù)組定義引用類(lèi),不會(huì)觸發(fā)該類(lèi)的初始化罚随。
即:A[ ] arr = new A[7]晨炕,不會(huì)實(shí)例化A。
- 接口與類(lèi)的區(qū)別
與上述中的【有且只有5種情況】毫炉,只有第3)種情況不同。一個(gè)類(lèi)初始化時(shí)削罩,要求先初始化其父類(lèi)瞄勾,但一個(gè)接口在初始化時(shí),并不要求其父接口全部都完成初始化弥激,只有在真正使用到父類(lèi)接口的時(shí)候才會(huì)初始化进陡。
1. 加載
加載階段虛擬機(jī)完成的3件事:
1)通過(guò)一個(gè)類(lèi)的全限定名來(lái)獲取定義此類(lèi)的二進(jìn)制字節(jié)流。 獲取的方式有:
??(1)從ZIP包中讀取微服,如:jar趾疚、ear、war以蕴。
??(2)從網(wǎng)絡(luò)中獲取糙麦,如:Applet。
??(3)運(yùn)行時(shí)計(jì)算生成丛肮,如:動(dòng)態(tài)代理赡磅。
??(4)其它文件生成,如:由JSP文件生成對(duì)應(yīng)的Class類(lèi)宝与。
2)將這個(gè)字節(jié)流代表的結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu)焚廊。
3)在內(nèi)存中生成一個(gè)代表這個(gè)類(lèi)的Class對(duì)象冶匹,作為方法區(qū)中這個(gè)類(lèi)的各種數(shù)據(jù)的訪問(wèn)入口∨匚粒【該Class對(duì)象可以在Java堆中嚼隘,也可以像HotSpot虛擬機(jī)那樣放在方法區(qū)】
數(shù)組類(lèi)本身不通過(guò)類(lèi)加載器創(chuàng)建,它由Java虛擬機(jī)直接創(chuàng)建袒餐。但數(shù)組類(lèi)的元素是由類(lèi)加載器去創(chuàng)建飞蛹。
2. 驗(yàn)證
為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全匿乃。Class文件并不一定要求用Java源碼編譯而出來(lái)桩皿,比如:用十六進(jìn)制編輯器直接編寫(xiě)產(chǎn)生Class文件,可能會(huì)存在數(shù)組越界幢炸,類(lèi)型轉(zhuǎn)換異常的問(wèn)題泄隔,盡管使用Java編譯器編譯時(shí),會(huì)拒絕編譯宛徊。
驗(yàn)證階段對(duì)虛擬機(jī)來(lái)說(shuō)不是必要的佛嬉,對(duì)于已經(jīng)被反復(fù)使用和驗(yàn)證過(guò)的代碼,可以通過(guò)參數(shù)來(lái)關(guān)閉類(lèi)的驗(yàn)證闸天,以縮短虛擬機(jī)類(lèi)加載的時(shí)間暖呕。
3. 準(zhǔn)備
準(zhǔn)備階段正式為【類(lèi)變量】分配內(nèi)存并設(shè)置【類(lèi)變量初始值】的階段。注意:這時(shí)只對(duì)類(lèi)變量【被static修飾的變量】進(jìn)行內(nèi)存分配苞氮,在【方法區(qū)】中進(jìn)行分配湾揽。這個(gè)階段不包含實(shí)例變量。
public static int value = 123;
準(zhǔn)備階段只是為value設(shè)初始值0而不是123笼吟,因?yàn)檫@時(shí)候尚未開(kāi)始執(zhí)行任何Java方法库物,而把value賦值為123的putstatic指令是程序被編譯后,存放于類(lèi)構(gòu)造器<clinit>()方法中贷帮,所以把value賦值為123的動(dòng)作將在【初始化】階段執(zhí)行戚揭。
public static final int value = 123;
由于被final修飾珍策,編譯時(shí)javac將會(huì)為value生成ConstantValue屬性姑隅,在準(zhǔn)備階段虛擬機(jī)就會(huì)根據(jù)ConstantValue的設(shè)置將value賦值為123为迈。
所以由final修飾的static靜態(tài)常量叙淌,在準(zhǔn)備階段就會(huì)賦用戶定義的值多矮。
4. 解析
解析階段是虛擬機(jī)將常量池內(nèi)的【符號(hào)引用替換為直接引用】的過(guò)程躯砰。
invokedynamic指令用于動(dòng)態(tài)語(yǔ)言支持活箕,必須等到程序?qū)嶋H運(yùn)行到這條指令的時(shí)候魂仍,解析動(dòng)作才能進(jìn)行沃但。相對(duì)的刮便,其余可觸發(fā)解析的指令都是【靜態(tài)】的,可以在剛剛完成加載階段绽慈,還沒(méi)有開(kāi)始執(zhí)行代碼時(shí)就進(jìn)行解析恨旱。
5. 初始化
類(lèi)初始化階段是類(lèi)加載過(guò)程的最后一步辈毯,前面的類(lèi)加載過(guò)程中,除了在加載階段用戶應(yīng)用程序可以通過(guò)自定義類(lèi)加載器參與之外搜贤,其余都是有虛擬機(jī)主導(dǎo)和控制谆沃。初始化階段才是真正執(zhí)行Java程序代碼。在準(zhǔn)備階段仪芒,變量已經(jīng)賦過(guò)一次系統(tǒng)要求的初始值唁影,在初始化階段則是初始化程序員主動(dòng)計(jì)劃的賦值。
類(lèi)構(gòu)造器<clinit>()
實(shí)例構(gòu)造器<init>()
<clinit>()方法是由編譯器自動(dòng)收集類(lèi)中的所有【類(lèi)變量掂名,非final修飾】的賦值動(dòng)作和【靜態(tài)語(yǔ)句塊static{ }】中的語(yǔ)句合并產(chǎn)生的据沈,收集的順序由語(yǔ)句在源文件中出現(xiàn)的順序決定〗让铮【靜態(tài)語(yǔ)句塊中只能訪問(wèn)到定義在靜態(tài)語(yǔ)句塊之前的變量锌介,對(duì)于定義在它之后的變量,只能賦值猾警,但不能訪問(wèn)】孔祸。如:
public class Test {
static {
i = 0; //ok,可以對(duì)i進(jìn)行賦值
System.out.print(i); //error发皿,不能訪問(wèn)
}
static int i = 1;
}
<clinit>()方法崔慧,虛擬機(jī)會(huì)保證先執(zhí)行父類(lèi)中的方法,而不需要顯示調(diào)用穴墅。因此虛擬機(jī)中第一個(gè)被執(zhí)行的<clinit>()方法的類(lèi)肯定是Object惶室。
接口中不能使用靜態(tài)語(yǔ)句塊,并且執(zhí)行接口的<clinit>()方法不需要先執(zhí)行父接口的<clinit>()方法玄货。只有當(dāng)父接口中定義的變量使用時(shí)拇涤,父接口才會(huì)初始化。另外誉结,接口的實(shí)現(xiàn)類(lèi)在初始化時(shí)也一樣不會(huì)執(zhí)行接口的<clinit>()方法。
虛擬機(jī)會(huì)保證一個(gè)類(lèi)的<clinit>()方法在多線程環(huán)境中被正確的加鎖券躁、同步惩坑,如果多個(gè)線程同時(shí)去初始化一個(gè)類(lèi),那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類(lèi)的<clinit>()方法也拜,其他線程都阻塞等待以舒,直到活動(dòng)線程執(zhí)行<clinit>()方法完畢。執(zhí)行完畢后慢哈,其它線程不會(huì)再執(zhí)行<clinit>()方法蔓钟,因?yàn)橥粋€(gè)類(lèi)加載器下,一個(gè)類(lèi)型只會(huì)初始化一次卵贱。