類的加載過程
java源代碼被編譯成class字節(jié)碼,JVM把描述類數(shù)據(jù)的字節(jié)碼.Class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的java類型,這就是虛擬機(jī)的類加載機(jī)制。
類從被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止,它的生命周期包括了:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(PReparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸載(Unloading)七個(gè)階段,其中驗(yàn)證、準(zhǔn)備、解析三個(gè)部分統(tǒng)稱鏈接。 加載(裝載)、驗(yàn)證、準(zhǔn)備、初始化和卸載這五個(gè)階段順序是固定的,類的加載過程必須按照這種順序開始,而解析階段不一定;它在某些情況下可以在初始化之后再開始,這是為了運(yùn)行時(shí)動(dòng)態(tài)綁定特性(也稱為動(dòng)態(tài)綁定或者晚期綁定,例如重寫)。
1.加載: 在加載階段,虛擬機(jī)主要完成三件事: 1.通過一個(gè)類的全限定名來獲取定義此類的二進(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對象,作為方法區(qū)域數(shù)據(jù)的訪問入口 相對于類加載過程的其他階段,加載階段(準(zhǔn)備地說,是加載階段中獲取類的二進(jìn)制字節(jié)流的動(dòng)作)是開發(fā)期可控性最強(qiáng)的階段,因?yàn)榧虞d階段可以使用系統(tǒng)提供的類加載器(ClassLoader)來完成,也可以由用戶自定義的類加載器完成,開發(fā)人員可以通過定義自己的類加載器去控制字節(jié)流的獲取方式。 加載階段完成后,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲(chǔ)在方法區(qū)之中,方法區(qū)中的數(shù)據(jù)存儲(chǔ)格式有虛擬機(jī)實(shí)現(xiàn)自行定義,虛擬機(jī)并未規(guī)定此區(qū)域的具體數(shù)據(jù)結(jié)構(gòu)。然后在java堆中實(shí)例化一個(gè)java.lang.Class類的對象,這個(gè)對象作為程序訪問方法區(qū)中的這些類型數(shù)據(jù)的外部接口。
2.驗(yàn)證: 驗(yàn)證階段作用是保證Class文件的字節(jié)流包含的信息符合JVM規(guī)范,不會(huì)給JVM造成危害。如果驗(yàn)證失敗,就會(huì)拋出一個(gè)java.lang.VerifyError異常或其子類異常。驗(yàn)證過程分為四個(gè)階段 1.文件格式驗(yàn)證:驗(yàn)證字節(jié)流文件是否符合Class文件格式的規(guī)范,并且能被當(dāng)前虛擬機(jī)正確的處理。 2.元數(shù)據(jù)驗(yàn)證:是對字節(jié)碼描述的信息進(jìn)行語義分析,以保證其描述的信息符合Java語言的規(guī)范。 3.字節(jié)碼驗(yàn)證:主要是進(jìn)行數(shù)據(jù)流和控制流的分析,保證被校驗(yàn)類的方法在運(yùn)行時(shí)不會(huì)危害虛擬機(jī)。 4.符號(hào)引用驗(yàn)證:符號(hào)引用驗(yàn)證發(fā)生在虛擬機(jī)將符號(hào)引用轉(zhuǎn)化為直接引用的時(shí)候,這個(gè)轉(zhuǎn)化動(dòng)作將在解析階段中發(fā)生。
3.準(zhǔn)備: 準(zhǔn)備階段為變量分配內(nèi)存并設(shè)置類變量的初始化。在這個(gè)階段分配的僅為類的變量(static修飾的變量),而不包括類的實(shí)例變量。對已非final的變量,JVM會(huì)將其設(shè)置成“零值”,而不是其賦值語句的值: pirvate static int size = 12; 那么在這個(gè)階段,size的值為0,而不是12。 final修飾的類變量將會(huì)賦值成真實(shí)的值。
4.解析: 解析階段是虛擬機(jī)常量池內(nèi)的符號(hào)引用替換為直接引用的過程。 符號(hào)引用:符號(hào)引用是一組符號(hào)來描述所引用的目標(biāo)對象,符號(hào)可以是任何形式的字面量,只要使用時(shí)能無歧義地定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān),引用的目標(biāo)對象并不一定已經(jīng)加載到內(nèi)存中。 直接引用:直接引用可以是直接指向目標(biāo)對象的指針、相對偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。直接引用是與虛擬機(jī)內(nèi)存布局實(shí)現(xiàn)相關(guān)的,同一個(gè)符號(hào)引用在不同虛擬機(jī)實(shí)例上翻譯出來的直接引用一般不會(huì)相同,如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。 虛擬機(jī)規(guī)范并沒有規(guī)定解析階段發(fā)生的具體時(shí)間,只要求了在執(zhí)行anewarry、checkcast、getfield、instanceof、invokeinterface、invokespecial、invokestatic、invokevirtual、multianewarray、new、putfield和putstatic這13個(gè)用于操作符號(hào)引用的字節(jié)碼指令之前,先對它們使用的符號(hào)引用進(jìn)行解析,所以虛擬機(jī)實(shí)現(xiàn)會(huì)根據(jù)需要來判斷,到底是在類被加載器加載時(shí)就對常量池中的符號(hào)引用進(jìn)行解析,還是等到一個(gè)符號(hào)引用將要被使用前才去解析它。 解析的動(dòng)作主要針對類或接口、字段、類方法、接口方法四類符號(hào)引用進(jìn)行。分別對應(yīng)編譯后常量池內(nèi)的CONSTANT_Class_Info、CONSTANT_Fieldref_Info、CONSTANT_Methodef_Info、CONSTANT_InterfaceMethoder_Info四種常量類型。 1.類、接口的解析 2.字段解析 3.類方法解析 4.接口方法解析
5.初始化: 類的初始化階段是類加載過程的最后一步,在準(zhǔn)備階段,類變量已賦過一次系統(tǒng)要求的初始值,而在初始化階段,則是根據(jù)程序員通過程序制定的主觀計(jì)劃去初始化類變量和其他資源,或者可以從另外一個(gè)角度來表達(dá):初始化階段是執(zhí)行類構(gòu)造器()方法的過程。在以下四種情況下初始化過程會(huì)被觸發(fā)執(zhí)行: 1.遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒有進(jìn)行過初始化,則需先觸發(fā)其初始化。生成這4條指令的最常見的java代碼場景是:使用new關(guān)鍵字實(shí)例化對象、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用類的靜態(tài)方法的時(shí)候。 2.使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用的時(shí)候 3.當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化、則需要先出發(fā)其父類的初始化 4.jvm啟動(dòng)時(shí),用戶指定一個(gè)執(zhí)行的主類(包含main方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)類 在上面準(zhǔn)備階段 public static int value = 12; 在準(zhǔn)備階段完成后 value的值為0,而在初始化階調(diào)用了類構(gòu)造器()方法,這個(gè)階段完成后value的值為12。 *類構(gòu)造器()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語句塊(static塊)中的語句合并產(chǎn)生的,編譯器收集的順序是由語句在源文件中出現(xiàn)的順序所決定的,靜態(tài)語句塊中只能訪問到定義在靜態(tài)語句塊之前的變量,定義在它之后的變量,在前面的靜態(tài)語句快可以賦值,但是不能訪問。 *類構(gòu)造器()方法與類的構(gòu)造函數(shù)(實(shí)例構(gòu)造函數(shù)()方法)不同,它不需要顯式調(diào)用父類構(gòu)造,虛擬機(jī)會(huì)保證在子類()方法執(zhí)行之前,父類的()方法已經(jīng)執(zhí)行完畢。因此在虛擬機(jī)中的第一個(gè)執(zhí)行的()方法的類肯定是java.lang.Object。 *由于父類的()方法先執(zhí)行,也就意味著父類中定義的靜態(tài)語句快要優(yōu)先于子類的變量賦值操作。 *()方法對于類或接口來說并不是必須的,如果一個(gè)類中沒有靜態(tài)語句,也沒有變量賦值的操作,那么編譯器可以不為這個(gè)類生成()方法。 *接口中不能使用靜態(tài)語句塊,但接口與類不太能夠的是,執(zhí)行接口的()方法不需要先執(zhí)行父接口的()方法。只有當(dāng)父接口中定義的變量被使用時(shí),父接口才會(huì)被初始化。另外,接口的實(shí)現(xiàn)類在初始化時(shí)也一樣不會(huì)執(zhí)行接口的()方法。 *虛擬機(jī)會(huì)保證一個(gè)類的()方法在多線程環(huán)境中被正確加鎖和同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類,那么只會(huì)有一個(gè)線程執(zhí)行這個(gè)類的()方法,其他線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行()方法完畢。如果一個(gè)類的()方法中有耗時(shí)很長的操作,那就可能造成多個(gè)進(jìn)程阻塞。
6.使用: 新線程—程序計(jì)數(shù)器—-jvm棧執(zhí)行(對象引用)—–堆內(nèi)存(直接引用)—-方法區(qū)
7.卸載: GC垃圾回收
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注