java类验证和装载顺序_Java类的加载机制和双亲委派模型
Java類的加載機(jī)制和雙親委派模型
1類的加載機(jī)制
類從被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止,它的整個生命周期包括了:加載(Loading)、驗證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(using)、和卸載(Unloading)七個階段。其中驗證、準(zhǔn)備和解析三個部分統(tǒng)稱為連接(Linking),這七個階段的發(fā)生順序如下圖所示:
如上圖所示,加載、驗證、準(zhǔn)備、初始化和卸載這五個階段的順序是確定的,類的加載過程必須按照這個順序來按部就班地開始,而解析階段則不一定,它在某些情況下可以在初始化階段后再開始。
類的生命周期的每一個階段通常都是互相交叉混合式進(jìn)行的,通常會在一個階段執(zhí)行的過程中調(diào)用或激活另外一個階段。
1.1裝載階段
在裝載階段,虛擬機(jī)主要完成三件事:
1、通過一個類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。
2、將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)域的運行時數(shù)據(jù)結(jié)構(gòu)。
3、在Java堆中生成一個代表這個類的java.lang.Class(HelloWorld.class)對象,作為方法區(qū)域數(shù)據(jù)的訪問入口
1.2驗證階段
驗證階段作用是保證Class文件的字節(jié)流包含的信息符合JVM規(guī)范,不會給JVM造成危害。如果驗證失敗,就會拋出一個java.lang.VerifyError異常或其子類異常。驗證過程分為四個階段
1、
文件格式驗證
驗證字節(jié)流文件是否符合Class文件格式的規(guī)范,并且能被當(dāng)前虛擬機(jī)正確的處理。
2、元數(shù)據(jù)驗證
是對字節(jié)碼描述的信息進(jìn)行語義分析,以保證其描述的信息符合Java語言的規(guī)范。
3、字節(jié)碼驗證
主要是進(jìn)行數(shù)據(jù)流和控制流的分析,保證被校驗類的方法在運行時不會危害虛擬機(jī)。
4、符號引用驗證
符號引用驗證發(fā)生在虛擬機(jī)將符號引用轉(zhuǎn)化為直接引用的時候,這個轉(zhuǎn)化動作將在解析階段中發(fā)生。
1.3準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量(static修飾的變量)初始值的階段,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配。
注釋:
1、這時候進(jìn)行內(nèi)存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨著對象一塊分配在Java堆中。
2、這里所設(shè)置的初始值通常情況下是數(shù)據(jù)類型默認(rèn)的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。
3、對已非final的變量,JVM會將其設(shè)置成“零值”,而不是其賦值語句的值:
pirvate static int size = 12;
那么在這個階段,size的值為0,而不是12。 final修飾的類變量將會賦值成真實的值。
1.4解析
解析階段是虛擬機(jī)將常量池內(nèi)的符號引用替換為直接引用的過程。
符號引用:符號引用以一組符號來描述所引用的目標(biāo),符號引用可以是任何形式的字面量,符號引用與虛擬機(jī)實現(xiàn)的內(nèi)存布局無關(guān),引用的目標(biāo)并不一定已經(jīng)在內(nèi)存中。
直接引用:直接引用可以是直接指向目標(biāo)的指針、相對偏移量或是一個能間接定位到目標(biāo)的句柄。直接引用是與虛擬機(jī)實現(xiàn)的內(nèi)存布局相關(guān)的,同一個符號引用在不同的虛擬機(jī)實例上翻譯出來的直接引用一般都不相同,如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。
1、類或接口的解析:判斷所要轉(zhuǎn)化成的直接引用是對數(shù)組類型,還是普通的對象類型的引用,從而進(jìn)行不同的解析。
2、字段解析:對字段進(jìn)行解析時,會先在本類中查找是否包含有簡單名稱和字段描述符都與目標(biāo)相匹配的字段,如果有,則查找結(jié)束;如果沒有,則會按照繼承關(guān)系從上往下遞歸搜索該類所實現(xiàn)的各個接口和它們的父接口,還沒有,則按照繼承關(guān)系從上往下遞歸搜索其父類,直至查找結(jié)束。
3、類方法解析:對類方法的解析與對字段解析的搜索步驟差不多,只是多了判斷該方法所處的是類還是接口的步驟,而且對類方法的匹配搜索,是先搜索父類,再搜索接口。
4、接口方法解析:與類方法解析步驟類似,只是接口不會有父類,因此,只遞歸向上搜索父接口就行了。
1.5初始化
類初始化階段是類加載過程的最后一步,前面的類加載過程中,除了加載(Loading)階段用戶應(yīng)用程序可以通過自定義類加載器參與之外,其余動作完全由虛擬機(jī)主導(dǎo)和控制。到了初始化階段,才真正開始執(zhí)行類中定義的Java程序代碼。
初始化階段是執(zhí)行類構(gòu)造器()方法的過程。
1、()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊(static{}塊)中的語句合并產(chǎn)生的,編譯器收集的順序由語句在源文件中出現(xiàn)的順序所決定。
2、()方法與類的構(gòu)造函數(shù)不同,它不需要顯式地調(diào)用父類構(gòu)造器,虛擬機(jī)會保證在子類的()方法執(zhí)行之前,父類的()方法已經(jīng)執(zhí)行完畢,因此在虛擬機(jī)中第一個執(zhí)行的()方法的類一定是java.lang.Object。
3、由于父類的()方法先執(zhí)行,也就意味著父類中定義的靜態(tài)語句塊要優(yōu)先于子類的變量賦值操作。
4、()方法對于類或者接口來說并不是必需的,如果一個類中沒有靜態(tài)語句塊也沒有對變量的賦值操作,那么編譯器可以不為這個類生成()方法。
5、接口中可能會有變量賦值操作,因此接口也會生成()方法。但是接口與類不同,執(zhí)行接口的()方法不需要先執(zhí)行父接口的()方法。只有當(dāng)父接口中定義的變量被使用時,父接口才會被初始化。另外,接口的實現(xiàn)類在初始化時也不會執(zhí)行接口的()方法。
6、虛擬機(jī)會保證一個類的()方法在多線程環(huán)境中被正確地加鎖和同步。如果有多個線程去同時初始化一個類,那么只會有一個線程去執(zhí)行這個類的()方法,其它線程都需要阻塞等待,直到活動線程執(zhí)行()方法完畢。如果在一個類的()方法中有耗時很長的操作,那么就可能造成多個進(jìn)程阻塞。
1.6使用
Class初始化過程完后就可以被任意調(diào)用。
1.7卸載
JVM中的Class只有滿足以下三個條件,才能被GC回收,也就是該Class被卸載:
1、該類所有的實例都已經(jīng)被GC。
2、加載該類的ClassLoader實例已經(jīng)被GC。
3、該類的java.lang.Class對象沒有在任何地方被引用。
1.8研究類加載的意義
類加載是Java程序運行的第一步,研究類的加載有助于了解JVM執(zhí)行過程,并指導(dǎo)開發(fā)者采取更有效的措施配合程序執(zhí)行。
研究類加載機(jī)制的第二個目的是讓程序能動態(tài)的控制類加載,比如熱部署等,提高程序的靈活性和適應(yīng)性。
1.9加載類的開放性
類加載器(ClassLoader)是Java語言的一項創(chuàng)新,也是Java流行的一個重要原因。在類加載的第一階段“加載”過程中,需要通過一個類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流,完成這個動作的代碼塊就是類加載器。這一動作是放在Java虛擬機(jī)外部去實現(xiàn)的,以便讓應(yīng)用程序自己決定如何獲取所需的類。
虛擬機(jī)規(guī)范并沒有指明二進(jìn)制字節(jié)流要從一個Class文件獲取,或者說根本沒有指明從哪里獲取、怎樣獲取。這種開放使得Java在很多領(lǐng)域得到充分運用,例如:
1、從ZIP包中讀取,這很常見,成為JAR,EAR,WAR格式的基礎(chǔ)。
2、從網(wǎng)絡(luò)中獲取,最典型的應(yīng)用就是Applet。
3、運行時計算生成,最典型的是動態(tài)代理技術(shù),在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generateProxyClass來為特定接口生成形式為“*$Proxy”的代理類的二進(jìn)制字節(jié)流。
4、有其他文件生成,最典型的JSP應(yīng)用,由JSP文件生成對應(yīng)的Class類。
1.10類加載器與類的唯一性
類加載器雖然只用于實現(xiàn)類的加載動作,但是對于任意一個類,都需要由加載它的類加載器和這個類本身共同確立其在Java虛擬機(jī)中的唯一性。通俗的說,JVM中兩個類是否“相等”,首先就必須是同一個類加載器加載的,否則,即使這兩個類來源于同一個Class文件,被同一個虛擬機(jī)加載,只要類加載器不同,那么這兩個類必定是不相等的。
這里的“相等”,包括代表類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結(jié)果,也包括使用instanceof關(guān)鍵字做對象所屬關(guān)系判定等情況。
2類加載的時機(jī)
2.1主動引用
一個類被主動引用之后會觸發(fā)初始化過程(加載,驗證,準(zhǔn)備需再此之前開始)。
1、遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。生成這4條指令最常見的Java代碼場景是:使用new關(guān)鍵字實例化對象時、讀取或者設(shè)置一個類的靜態(tài)字段(被final修飾、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)時、以及調(diào)用一個類的靜態(tài)方法的時候。
2、使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用的時候,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。
3、當(dāng)初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要觸發(fā)父類的初始化。
4、當(dāng)虛擬機(jī)啟動時,用戶需要指定一個執(zhí)行的主類(包含main()方法的類),虛擬機(jī)會先初始化這個類。
5、當(dāng)使用jdk7+的動態(tài)語言支持時,如果java.lang.invoke.MethodHandle實例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個方法句柄所對應(yīng)的類沒有進(jìn)行過初始化,則需要先觸發(fā)器 初始化。(更多java動態(tài)類型語言資料:http://www.infoq.com/cn/articles/jdk-dynamically-typed-language)
2.2被動引用
一個類如果是被動引用的話,該類不會觸發(fā)初始化過程。
1、通過子類引用父類的靜態(tài)字段,不會導(dǎo)致子類初始化。對于靜態(tài)字段,只有直接定義該字段的類才會被初始化,因此當(dāng)我們通過子類來引用父類中定義的靜態(tài)字段時,只會觸發(fā)父類的初始化,而不會觸發(fā)子類的初始化。
2、通過數(shù)組定義來引用類,不會觸發(fā)此類的初始化。
3、常量在編譯階段會存入調(diào)用類的常量池中,本質(zhì)上沒有直接引用到定義常量的類,因此不會觸發(fā)定義常量的類的初始化。
3雙親委派模型
3.1從Java虛擬機(jī)的角度來說,只存在兩種不同的類加載器
1、啟動類加載器(Bootstrap ClassLoader),這個類加載器使用C++語言實現(xiàn)(HotSpot虛擬機(jī)中),是虛擬機(jī)自身的一部分。
2、所有其他的類加載器,這些類加載器都有Java語言實現(xiàn),獨立于虛擬機(jī)外部,并且全部繼承自java.lang.ClassLoader。
3.2從開發(fā)者的角度,類加載器可以細(xì)分為
1、啟動(Bootstrap)類加載器:負(fù)責(zé)將 Java_Home/lib下面的類庫加載到內(nèi)存中(比如rt.jar)。由于引導(dǎo)類加載器涉及到虛擬機(jī)本地實現(xiàn)細(xì)節(jié),開發(fā)者無法直接獲取到啟動類加載器的引用,所以不允許直接通過引用進(jìn)行操作。
2、標(biāo)準(zhǔn)擴(kuò)展(Extension)類加載器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實現(xiàn)的。它負(fù)責(zé)將Java_Home /lib/ext或者由系統(tǒng)變量 java.ext.dir指定位置中的類庫加載到內(nèi)存中。開發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類加載器。
3、應(yīng)用程序(Application)類加載器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實現(xiàn)的。它負(fù)責(zé)將系統(tǒng)類路徑(CLASSPATH)中指定的類庫加載到內(nèi)存中。開發(fā)者可以直接使用系統(tǒng)類加載器。由于這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般稱為系統(tǒng)(System)加載器。
4、自定義(Custom
ClassLoader)類加載器:應(yīng)用程序根據(jù)自身需要自定義的ClassLoader,如tomcat、jboss都會根據(jù)j2ee規(guī)范自行實現(xiàn)ClassLoader加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。
3.3雙親委派模型
啟動、標(biāo)準(zhǔn)擴(kuò)展、應(yīng)用程序和自定義類加載器,它們之間的層次關(guān)系被稱為類加載器的雙親委派模型。該模型要求除了頂層的啟動類加載器外,其余的類加載器都應(yīng)該有自己的父類加載器,而這種父子關(guān)系一般通過組合(Composition)關(guān)系來實現(xiàn),而不是通過繼承(Inheritance)。
3.4雙親委派模型的過程
某個特定的類加載器在接到加載類的請求時,首先將加載任務(wù)委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務(wù),就成功返回;只有父類加載器無法完成此加載任務(wù)時,才自己去加載。
使用雙親委派模型的好處在于Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系。例如類java.lang.Object,它存在在rt.jar中,無論哪一個類加載器要加載這個類,最終都是委派給處于模型最頂端的Bootstrap ClassLoader進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個類。相反,如果沒有雙親委派模型而是由各個類加載器自行加載的話,如果用戶編寫了一個java.lang.Object的同名類并放在ClassPath中,那系統(tǒng)中將會出現(xiàn)多個不同的Object類,程序?qū)⒒靵y。因此,如果開發(fā)者嘗試編寫一個與rt.jar類庫中重名的Java類,可以正常編譯,但是永遠(yuǎn)無法被加載運行。
參考鏈接:
總結(jié)
以上是生活随笔為你收集整理的java类验证和装载顺序_Java类的加载机制和双亲委派模型的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux php生产环境搭建,linu
- 下一篇: java美元兑换,(Java实现) 美元