JVM类加载机制详解(一)JVM类加载过程
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
首先Throws(拋出)幾個(gè)自己學(xué)習(xí)過程中一直疑惑的問題:
1、什么是類加載?什么時(shí)候進(jìn)行類加載?
2、什么是類初始化?什么時(shí)候進(jìn)行類初始化?
3、什么時(shí)候會為變量分配內(nèi)存?
4、什么時(shí)候會為變量賦默認(rèn)初值?什么時(shí)候會為變量賦程序設(shè)定的初值?
5、類加載器是什么?
6、如何編寫一個(gè)自定義的類加載器?
?
首先,在代碼編譯后,就會生成JVM(Java虛擬機(jī))能夠識別的二進(jìn)制字節(jié)流文件(*.class)。而JVM把Class文件中的類描述數(shù)據(jù)從文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析、初始化,使這些數(shù)據(jù)最終成為可以被JVM直接使用的Java類型,這個(gè)說來簡單但實(shí)際復(fù)雜的過程叫做JVM的類加載機(jī)制。
?
Class文件中的“類”從加載到JVM內(nèi)存中,到卸載出內(nèi)存過程有七個(gè)生命周期階段。類加載機(jī)制包括了前五個(gè)階段。
如下圖所示:
其中,加載、驗(yàn)證、準(zhǔn)備、初始化、卸載的開始順序是確定的,注意,只是按順序開始,進(jìn)行與結(jié)束的順序并不一定。解析階段可能在初始化之后開始。
?
另外,類加載無需等到程序中“首次使用”的時(shí)候才開始,JVM預(yù)先加載某些類也是被允許的。(類加載的時(shí)機(jī))
?
一、類的加載
我們平常說的加載大多不是指的類加載機(jī)制,只是類加載機(jī)制中的第一步加載。在這個(gè)階段,JVM主要完成三件事:
?
1、通過一個(gè)類的全限定名(包名與類名)來獲取定義此類的二進(jìn)制字節(jié)流(Class文件)。而獲取的方式,可以通過jar包、war包、網(wǎng)絡(luò)中獲取、JSP文件生成等方式。
2、將這個(gè)字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。這里只是轉(zhuǎn)化了數(shù)據(jù)結(jié)構(gòu),并未合并數(shù)據(jù)。(方法區(qū)就是用來存放已被加載的類信息,常量,靜態(tài)變量,編譯后的代碼的運(yùn)行時(shí)內(nèi)存區(qū)域)
3、在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口。這個(gè)Class對象并沒有規(guī)定是在Java堆內(nèi)存中,它比較特殊,雖為對象,但存放在方法區(qū)中。
?
二、類的連接
類的加載過程后生成了類的java.lang.Class對象,接著會進(jìn)入連接階段,連接階段負(fù)責(zé)將類的二進(jìn)制數(shù)據(jù)合并入JRE(Java運(yùn)行時(shí)環(huán)境)中。類的連接大致分三個(gè)階段。
1、驗(yàn)證:驗(yàn)證被加載后的類是否有正確的結(jié)構(gòu),類數(shù)據(jù)是否會符合虛擬機(jī)的要求,確保不會危害虛擬機(jī)安全。
2、準(zhǔn)備:為類的靜態(tài)變量(static filed)在方法區(qū)分配內(nèi)存,并賦默認(rèn)初值(0值或null值)。如static int a = 100;
靜態(tài)變量a就會在準(zhǔn)備階段被賦默認(rèn)值0。
對于一般的成員變量是在類實(shí)例化時(shí)候,隨對象一起分配在堆內(nèi)存中。
另外,靜態(tài)常量(static final filed)會在準(zhǔn)備階段賦程序設(shè)定的初值,如static final int a = 666; ?靜態(tài)常量a就會在準(zhǔn)備階段被直接賦值為666,對于靜態(tài)變量,這個(gè)操作是在初始化階段進(jìn)行的。
3、解析:將類的二進(jìn)制數(shù)據(jù)中的符號引用換為直接引用。
?
三、類的初始化
類初始化是類加載的最后一步,除了加載階段,用戶可以通過自定義的類加載器參與,其他階段都完全由虛擬機(jī)主導(dǎo)和控制。到了初始化階段才真正執(zhí)行Java代碼。
類的初始化的主要工作是為靜態(tài)變量賦程序設(shè)定的初值。
如static int a = 100;在準(zhǔn)備階段,a被賦默認(rèn)值0,在初始化階段就會被賦值為100。
?
Java虛擬機(jī)規(guī)范中嚴(yán)格規(guī)定了有且只有五種情況必須對類進(jìn)行初始化:
1、使用new字節(jié)碼指令創(chuàng)建類的實(shí)例,或者使用getstatic、putstatic讀取或設(shè)置一個(gè)靜態(tài)字段的值(放入常量池中的常量除外),或者調(diào)用一個(gè)靜態(tài)方法的時(shí)候,對應(yīng)類必須進(jìn)行過初始化。
2、通過java.lang.reflect包的方法對類進(jìn)行反射調(diào)用的時(shí)候,如果類沒有進(jìn)行過初始化,則要首先進(jìn)行初始化。
3、當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類沒有進(jìn)行過初始化,則首先觸發(fā)父類初始化。
4、當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)主類(包含main()方法的類),虛擬機(jī)會首先初始化這個(gè)類。
5、使用jdk1.7的動(dòng)態(tài)語言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,并且這個(gè)方法句柄對應(yīng)的類沒有進(jìn)行初始化,則需要先觸發(fā)其初始化。
?
注意,虛擬機(jī)規(guī)范使用了“有且只有”這個(gè)詞描述,這五種情況被稱為“主動(dòng)引用”,除了這五種情況,所有其他的類引用方式都不會觸發(fā)類初始化,被稱為“被動(dòng)引用”。
?
被動(dòng)引用的例子一:
通過子類引用父類的靜態(tài)字段,對于父類屬于“主動(dòng)引用”的第一種情況,對于子類,沒有符合“主動(dòng)引用”的情況,故子類不會進(jìn)行初始化。代碼如下:
?
[java]?view plain?copy
輸出結(jié)果:
?
被動(dòng)引用的例子之二:
通過數(shù)組來引用類,不會觸發(fā)類的初始化,因?yàn)槭菙?shù)組new,而類沒有被new,所以沒有觸發(fā)任何“主動(dòng)引用”條款,屬于“被動(dòng)引用”。代碼如下:
?
[java]?view plain?copy
?
?沒有任何結(jié)果輸出!
?
?
被動(dòng)引用的例子之三:
剛剛講解時(shí)也提到,靜態(tài)常量在編譯階段就會被存入調(diào)用類的常量池中,不會引用到定義常量的類,這是一個(gè)特例,需要特別記憶,不會觸發(fā)類的初始化!
?
[java]?view plain?copy
轉(zhuǎn)載于:https://my.oschina.net/u/2543341/blog/1504033
總結(jié)
以上是生活随笔為你收集整理的JVM类加载机制详解(一)JVM类加载过程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1069: [SCOI2007]最大土地
- 下一篇: Chipscope使用