1.虛擬機(jī)如何加載這些Class文件?(類加載的過(guò)程)
2.Class文件中的信息進(jìn)入到虛擬機(jī)后會(huì)發(fā)生什么變化?
Java虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存访忿,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)细疚、轉(zhuǎn)換解析和初始化毕莱,最終形成可以被虛擬機(jī)直接使用的Java類型储耐,這就是虛擬機(jī)的加載機(jī)制动分。
類從被加載到虛擬機(jī)內(nèi)存中開(kāi)始郑气,到卸載出內(nèi)存為止怠堪,它的整個(gè)生命周期包括了:加載(Loading)芽偏、驗(yàn)證(Verification)雷逆、準(zhǔn)備(Preparation)、解析(Resolution)哮针、初始化(Initialization)关面、使用(using)坦袍、和卸載(Unloading)七個(gè)階段。其中驗(yàn)證等太、準(zhǔn)備和解析三個(gè)部分統(tǒng)稱為連接(Linking)捂齐,這七個(gè)階段的發(fā)生順序如下圖所示:
如上圖所示,加載缩抡、驗(yàn)證奠宜、準(zhǔn)備、初始化和卸載這五個(gè)階段的順序是確定的瞻想,類的加載過(guò)程必須按照這個(gè)順序來(lái)按部就班地開(kāi)始压真,而解析階段則不一定,它在某些情況下可以在初始化階段后再開(kāi)始蘑险。
類的生命周期的每一個(gè)階段通常都是互相交叉混合式進(jìn)行的滴肿,通常會(huì)在一個(gè)階段執(zhí)行的過(guò)程中調(diào)用或激活另外一個(gè)階段。
一佃迄、類加載的時(shí)機(jī)
主動(dòng)引用:一個(gè)類被主動(dòng)引用之后會(huì)觸發(fā)初始化過(guò)程(加載泼差,驗(yàn)證,準(zhǔn)備需再此之前開(kāi)始)
必須馬上對(duì)類進(jìn)行初始化
1)遇到new呵俏、getstatic堆缘、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒(méi)有進(jìn)行過(guò)初始化普碎,則需要先觸發(fā)其初始化吼肥。生成這4條指令最常見(jiàn)的Java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象時(shí)、讀取或者設(shè)置一個(gè)類的靜態(tài)字段(被final修飾麻车、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)時(shí)缀皱、以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候。
2)使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候绪氛,如果類沒(méi)有進(jìn)行過(guò)初始化唆鸡,則需要先觸發(fā)其初始化涝影。
3)當(dāng)初始化一個(gè)類的時(shí)候枣察,如果發(fā)現(xiàn)其父類還沒(méi)有進(jìn)行過(guò)初始化,則需要觸發(fā)父類的初始化燃逻。
4)當(dāng)虛擬機(jī)啟動(dòng)時(shí)序目,用戶需要指定一個(gè)執(zhí)行的主類(包含main()方法的類),虛擬機(jī)會(huì)先初始化這個(gè)類伯襟。
5)當(dāng)使用jdk7+的動(dòng)態(tài)語(yǔ)言支持時(shí)猿涨,如果java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic姆怪、REF_invokeStatic的方法句柄叛赚,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒(méi)有進(jìn)行過(guò)初始化澡绩,則需要先觸發(fā)器初始化。
被動(dòng)引用:一個(gè)類如果是被動(dòng)引用的話俺附,該類不會(huì)觸發(fā)初始化過(guò)程
1)通過(guò)子類引用父類的靜態(tài)字段肥卡,不會(huì)導(dǎo)致子類初始化。對(duì)于靜態(tài)字段事镣,只有直接定義該字段的類才會(huì)被初始化步鉴,因此當(dāng)我們通過(guò)子類來(lái)引用父類中定義的靜態(tài)字段時(shí),只會(huì)觸發(fā)父類的初始化璃哟,而不會(huì)觸發(fā)子類的初始化氛琢。 只有定義了這個(gè)字段的類才會(huì)被初始化
2)通過(guò)數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)此類的初始化随闪。
3)常量在編譯階段會(huì)存入調(diào)用類的常量池中阳似,本質(zhì)上沒(méi)有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化铐伴。
二障般、類加載過(guò)程
1、加載
在加載階段盛杰,虛擬機(jī)需要完成以下三件事情:
1.通過(guò)一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流挽荡。
2.將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
3.在java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象即供,作為方法區(qū)這些數(shù)據(jù)的訪問(wèn)入口定拟。
相對(duì)于類加載過(guò)程的其他階段,加載階段是開(kāi)發(fā)期相對(duì)來(lái)說(shuō)可控性比較強(qiáng)逗嫡,該階段既可以使用系統(tǒng)提供的類加載器完成青自,也可以由用戶自定義的類加載器來(lái)完成,開(kāi)發(fā)人員可以通過(guò)定義自己的類加載器去控制字節(jié)流的獲取方式驱证。
2延窜、驗(yàn)證
驗(yàn)證的目的是為了確保Class文件中的字節(jié)流包含的信息符合當(dāng)前虛擬機(jī)的要求,而且不會(huì)危害虛擬機(jī)自身的安全抹锄。不同的虛擬機(jī)對(duì)類驗(yàn)證的實(shí)現(xiàn)可能會(huì)有所不同逆瑞,但大致都會(huì)完成以下四個(gè)階段的驗(yàn)證:文件格式的驗(yàn)證、元數(shù)據(jù)的驗(yàn)證伙单、字節(jié)碼驗(yàn)證和符號(hào)引用驗(yàn)證获高。
1)文件格式的驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理吻育,該驗(yàn)證的主要目的是保證輸入的字節(jié)流能正確地解析并存儲(chǔ) 于方法區(qū)之內(nèi)念秧。經(jīng)過(guò)該階段的驗(yàn)證后,字節(jié)流才會(huì)進(jìn)入內(nèi)存的方法區(qū)中進(jìn)行存儲(chǔ)布疼,后面的三個(gè)驗(yàn)證都是基于方法區(qū)的存儲(chǔ)結(jié)構(gòu)進(jìn)行的摊趾。
2)元數(shù)據(jù)驗(yàn)證:對(duì)類的元數(shù)據(jù)信息進(jìn)行語(yǔ)義校驗(yàn)(其實(shí)就是對(duì)類中的各數(shù)據(jù)類型進(jìn)行語(yǔ)法校驗(yàn))币狠,保證不存在不符合Java語(yǔ)法規(guī)范的元數(shù)據(jù)信息。
3)字節(jié)碼驗(yàn)證:該階段驗(yàn)證的主要工作是進(jìn)行數(shù)據(jù)流和控制流分析砾层,對(duì)類的方法體進(jìn)行校驗(yàn)分析总寻,以保證被校驗(yàn)的類的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的行為。
4)符號(hào)引用驗(yàn)證:這是最后一個(gè)階段的驗(yàn)證梢为,它發(fā)生在虛擬機(jī)將符號(hào)引用轉(zhuǎn)化為直接引用的時(shí)候(解析階段中發(fā)生該轉(zhuǎn)化渐行,后面會(huì)有講解),主要是對(duì)類自身以外的信息(常量池中的各種符號(hào)引用)進(jìn)行匹配性的校驗(yàn)铸董。
3祟印、準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配粟害。
注:
1)這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(static)蕴忆,而不包括實(shí)例變量,實(shí)例變量會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一塊分配在Java堆中悲幅。
2)這里所設(shè)置的初始值通常情況下是數(shù)據(jù)類型默認(rèn)的零值(如0套鹅、0L、null汰具、false等)卓鹿,而不是被在Java代碼中被顯式地賦予的值。
- 如果類字段的字段屬性表中存在ConstantValue屬性,在準(zhǔn)備階段該字段就會(huì)被初始化為ConstantValue所指定的值. final
4留荔、解析
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程吟孙。
符號(hào)引用(Symbolic Reference):符號(hào)引用以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)引用可以是任何形式的字面量聚蝶,符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān)杰妓,引用的目標(biāo)并不一定已經(jīng)在內(nèi)存中。
直接引用(Direct Reference):直接引用可以是直接指向目標(biāo)的指針碘勉、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄巷挥。直接引用是與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān)的,同一個(gè)符號(hào)引用在不同的虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般都不相同验靡,如果有了直接引用倍宾,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。
1晴叨、類或接口的解析:判斷所要轉(zhuǎn)化成的直接引用是對(duì)數(shù)組類型凿宾,還是普通的對(duì)象類型的引用,從而進(jìn)行不同的解析兼蕊。 加載引用的類
2、字段解析:對(duì)字段進(jìn)行解析時(shí)件蚕,會(huì)先在本類中查找是否包含有簡(jiǎn)單名稱和字段描述符都與目標(biāo)相匹配的字段孙技,如果有产禾,則查找結(jié)束;如果沒(méi)有牵啦,則會(huì)按照繼承關(guān)系從上往下遞歸搜索該類所實(shí)現(xiàn)的各個(gè)接口和它們的父接口亚情,還沒(méi)有,則按照繼承關(guān)系從上往下遞歸搜索其父類哈雏,直至查找結(jié)束楞件。 (注意查找順序)
3、類方法解析:對(duì)類方法的解析與對(duì)字段解析的搜索步驟差不多裳瘪,只是多了判斷該方法所處的是類還是接口的步驟土浸,而且對(duì)類方法的匹配搜索,是先搜索父類彭羹,再搜索接口黄伊。
4、接口方法解析:與類方法解析步驟類似派殷,只是接口不會(huì)有父類还最,因此,只遞歸向上搜索父接口就行了毡惜。
5拓轻、初始化
類初始化階段是類加載過(guò)程的最后一步,前面的類加載過(guò)程中经伙,除了加載(Loading)階段用戶應(yīng)用程序可以通過(guò)自定義類加載器參與之外悦即,其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制。到了初始化階段橱乱,才真正開(kāi)始執(zhí)行類中定義的Java程序代碼辜梳。
初始化階段是執(zhí)行類構(gòu)造器()方法的過(guò)程。
1)<clinit>()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static{}塊)中的語(yǔ)句合并產(chǎn)生的泳叠,編譯器收集的順序由語(yǔ)句在源文件中出現(xiàn)的順序所決定作瞄。(靜態(tài)語(yǔ)句塊中只能賦值,不能訪問(wèn),靜態(tài)語(yǔ)句塊只能訪問(wèn)到定義在靜態(tài)語(yǔ)句塊之前的變量)
2)<clinit>()方法與類的構(gòu)造函數(shù)不同,它不需要顯式地調(diào)用父類構(gòu)造器危纫,虛擬機(jī)會(huì)保證在子類的<clinit>()方法執(zhí)行之前宗挥,父類的()方法已經(jīng)執(zhí)行完畢,因此在虛擬機(jī)中第一個(gè)執(zhí)行的<clinit>()方法的類一定是java.lang.Object种蝶。
3)由于父類的<clinit>()方法先執(zhí)行契耿,也就意味著父類中定義的靜態(tài)語(yǔ)句塊要優(yōu)先于子類的變量賦值操作。
4)<clinit>()方法對(duì)于類或者接口來(lái)說(shuō)并不是必需的螃征,如果一個(gè)類中沒(méi)有靜態(tài)語(yǔ)句塊也沒(méi)有對(duì)變量的賦值操作搪桂,那么編譯器可以不為這個(gè)類生成<clinit>()方法。
5)接口中可能會(huì)有變量賦值操作,因此接口也會(huì)生成<clinit>()方法踢械。但是接口與類不同酗电,執(zhí)行接口的<clinit>()方法不需要先執(zhí)行父接口的<clinit>()方法。只有當(dāng)父接口中定義的變量被使用時(shí)内列,父接口才會(huì)被初始化撵术。另外,接口的實(shí)現(xiàn)類在初始化時(shí)也不會(huì)執(zhí)行接口的<clinit>()方法话瞧。
6)虛擬機(jī)會(huì)保證一個(gè)類的()方法在多線程環(huán)境中被正確地加鎖和同步嫩与。如果有多個(gè)線程去同時(shí)初始化一個(gè)類,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的<clinit>()方法交排,其它線程都需要阻塞等待划滋,直到活動(dòng)線程執(zhí)行<clinit>()方法完畢。如果在一個(gè)類的<clinit>()方法中有耗時(shí)很長(zhǎng)的操作个粱,那么就可能造成多個(gè)進(jìn)程阻塞古毛。
類加載器
通過(guò)一個(gè)類的全限定名來(lái)獲取描述此類的二進(jìn)制字節(jié)流
類與類加載器
類名稱空間,比較兩個(gè)類是否相等,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義.
三都许、雙親委派模型
JVM預(yù)定義的三種類型類加載器:
1)啟動(dòng)(Bootstrap)類加載器:是用本地代碼實(shí)現(xiàn)的類裝入器稻薇,它負(fù)責(zé)將 /lib下面的類庫(kù)加載到內(nèi)存中(比如rt.jar)。由于引導(dǎo)類加載器涉及到虛擬機(jī)本地實(shí)現(xiàn)細(xì)節(jié)胶征,開(kāi)發(fā)者無(wú)法直接獲取到啟動(dòng)類加載器的引用塞椎,所以不允許直接通過(guò)引用進(jìn)行操作。
2)標(biāo)準(zhǔn)擴(kuò)展(Extension)類加載器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實(shí)現(xiàn)的睛低。它負(fù)責(zé)將< Java_Runtime_Home >/lib/ext或者由系統(tǒng)變量 java.ext.dir指定位置中的類庫(kù)加載到內(nèi)存中案狠。開(kāi)發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類加載器。
3)系統(tǒng)(System)類加載器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實(shí)現(xiàn)的钱雷。它負(fù)責(zé)將系統(tǒng)類路徑(CLASSPATH)中指定的類庫(kù)加載到內(nèi)存中骂铁。開(kāi)發(fā)者可以直接使用系統(tǒng)類加載器。
雙親委派機(jī)制描述:
某個(gè)特定的類加載器在接到加載類的請(qǐng)求時(shí)罩抗,首先將加載任務(wù)委托給父類加載器拉庵,依次遞歸,如果父類加載器可以完成類加載任務(wù)套蒂,就成功返回钞支;只有父類加載器無(wú)法完成此加載任務(wù)時(shí),才自己去加載操刀。
/**
* Loads the class with the specified <a href="#name">binary name</a>. The
* default implementation of this method searches for classes in the
* following order:
*
* <p><ol>
*
* <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
* has already been loaded. </p></li>
*
* <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
* on the parent class loader. If the parent is <tt>null</tt> the class
* loader built-in to the virtual machine is used, instead. </p></li>
*
* <li><p> Invoke the {@link #findClass(String)} method to find the
* class. </p></li>
*
* </ol>
*
* <p> If the class was found using the above steps, and the
* <tt>resolve</tt> flag is true, this method will then invoke the {@link
* #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
*
* <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
* #findClass(String)}, rather than this method. </p>
*
* <p> Unless overridden, this method synchronizes on the result of
* {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
* during the entire class loading process.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @param resolve
* If <tt>true</tt> then resolve the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class could not be found
*/
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;
}
}
1.檢查類是否已經(jīng)被加載
2.如果沒(méi)有烁挟,就用父類的加載器
3.如果父類無(wú)法加載,然后調(diào)用findClass方法自行加載