(一)加載
加載階段,虛擬機完成三件事:
- 通過一個類的全限定名來獲取定義此類的二進制字節(jié)流;
- 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu);
- 在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。
其中,獲取字節(jié)流的方式:從Zip包中獲?。╦ar,ear,war等);從網(wǎng)絡(luò)中獲?。ˋpplet);運行時計算生成(動態(tài)代理技術(shù));其他文件(JSP文件);數(shù)據(jù)庫讀取。
注意:數(shù)組類由Java虛擬機直接創(chuàng)建,本身不通過類加載器創(chuàng)建;但是,數(shù)組類的元素類型是通過類加載器創(chuàng)建。
(二)驗證
1.作用:
驗證是連接階段的第一步,目的是確保Class文件的字節(jié)流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。如果驗證到輸入的字節(jié)流不符合Class文件格式的約束,jvm就拋出java.lang.VerfyError異?;蚱渥宇惍惓!?/p>
2.驗證階段大致經(jīng)歷4個階段的檢驗動作:
文件格式驗證、元數(shù)據(jù)驗證、字節(jié)碼驗證、符號引用驗證。第一個基于二進制字節(jié)流;后面三個是基于方法區(qū)的存儲結(jié)構(gòu)。
(1) 文件格式驗證
-作用:驗證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當前版本的虛擬機處理。
-如:是否以魔數(shù)0xCAFEBABE開頭;主次版本號是否在當前VM處理范圍之內(nèi);
常量池的常量中是否有不被支持的常量類型(tag標志);
-目的:保證輸入的字節(jié)流能正確地解析并存儲于方法區(qū)之內(nèi),格式上符合一個Java類型的要求。
(2) 元數(shù)據(jù)驗證
-作用:對字節(jié)碼描述的信息進行語義分析,保證其描述的信息符合Java語言規(guī)范的要求。
-如:這個類是否有父類(處Object外,所有類都有父類);
這個類的父類是否繼承了不允許被繼承的類(final修飾的類);
如果這個類不是抽象類,是否實現(xiàn)了父類或接口的所有要實現(xiàn)的方法;
類中字段、方法是否與父類產(chǎn)生矛盾(覆蓋父類final字段,方法重載不符合規(guī)則);
類中是否有父類、是否合法的繼承類、是否實現(xiàn)了父類或接口的方法、字段和方法是否與父類產(chǎn)生矛盾
-目的:保證對類的元數(shù)據(jù)信息進行語義校驗,保證不存在不符合Java語言規(guī)范的元數(shù)據(jù)信息。
(3) 字節(jié)碼驗證
-作用:對類的方法體進行校驗分析,保證被校驗類的方法在運行時不會做出危害VM安全的事件;
-如:保證任意時刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能配合工作;
保證跳轉(zhuǎn)指令不會跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上;
保證方法體中的類型轉(zhuǎn)換是有效的;
方法體:類型轉(zhuǎn)換是否有效、操作數(shù)棧和指令代碼是否匹配工作、跳轉(zhuǎn)指令是否跳轉(zhuǎn)到正確位置
-目的:通過數(shù)據(jù)流和控制流分析,確定程序語義是合法、符合邏輯的
(4) 符號引用驗證
-作用:發(fā)生在VM將符號引用轉(zhuǎn)化為直接引用的時候,轉(zhuǎn)化動作在連接的第三階段——解析階段
中發(fā)生。是對類自身以外的信息進行匹配性校驗。
-如:符號引用中通過字符串描述的全限定名是否能找到對應(yīng)的類;
在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段;
符號引用中的類、字段、方法的訪問性是否能被當前類訪問。
-目的:確保解析動作能正常執(zhí)行,如果無法通過符號引用驗證,拋java.langIncompatibleClassChangeErrot異常的子類,如java.langIllegalAccessError、java.lang.NoSuchFieldError和java.lang.NoSuchMethodError等。
魔數(shù):magic number是指每個Class文件的頭4個字節(jié);作用是確定這個文件是否為一個能被虛擬機接受的Class文件。
版本號:緊接著魔數(shù)的4個字節(jié)是存儲Class文件的版本號,第5、6字節(jié)是次版本好,第7、8字節(jié)是主版本號。版本號從45開始。
Class文件結(jié)構(gòu):
|魔數(shù) |次版本號 |主版本號| 常量池 |訪問標志|
4字節(jié) 2 2 2
全限定名、簡單名和描述符:
全限定名:如"com.ljy.TestClass"中改為"com/ljy/TestClass"
簡單名:沒有類型和參數(shù)修飾的方法或字段名稱:如inc()的簡單名是"inc"
描述符:描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(數(shù)量、類型及順序)和返回值。
如:"int[]"--->"[I" "void inc()"--->"()V" "java.lang.String[][]"--->"[[Ljava/lang/String;" 先參數(shù)列表,后返回值,每個數(shù)組用"[" "java.lang.String.toString()"--->"()Ljava/lang/String;"
(三)準備
作用:是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進行分配。
注意:
1.該階段進行內(nèi)存分配僅包括類變量(static修飾的),不包括實例變量,實例變量是在對象實例化隨著對象一起分配在Java堆中。
2.初始值是數(shù)據(jù)類型的零值:例如public static int value = 123; 準備階段:value=0; 初始化階段:value=123;
特殊:如果是final修飾的,準備階段就設(shè)定好初始化的值。
(四)解析
作用:VM將常量池內(nèi)的符號引用替換為直接引用的過程。
符號引用和直接引用:
符號引用:
以一組符號來描述所引用的目標,符號可以是任何形式的字面量,與VM實現(xiàn)的內(nèi)存布局無關(guān),引用的目標并不一定已經(jīng)加載到內(nèi)存中。
直接引用:
可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用于VM實現(xiàn)的內(nèi)存布局相關(guān),引用的目標必定已經(jīng)在內(nèi)存中存在。
解析的符號引用:類或接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用點限定符7類。分別對應(yīng)于常量池的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info、CONSTANT_MethodType_info、CONSTANT_MethodHandle_info和CONSTANT_InvokeDynamic_info7種常量類型。
(五)初始化
作用:類初始化階段是類加載過程最后一步,開始執(zhí)行類中定義的Java程序代碼(字節(jié)碼),是執(zhí)行類構(gòu)造器<clinit>()方法的過程。
<clinit>()方法:
1.<clinit>()是由編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊中的語句合并產(chǎn)生;
2.<clinit>()不需要顯式地調(diào)用父類構(gòu)造器,VM保證在調(diào)用<clinit>()之前就已經(jīng)執(zhí)行完父類的<clinit>(),所以VM中第一個被執(zhí)行的<clinit>()的類肯定是java.lang.Object;
3.父類中定義的靜態(tài)語句優(yōu)先于子類的變量賦值操作;
4.<clinit>()對于類或接口并不是必須的,若一個類中沒有靜態(tài)語句塊也沒有對變量的賦值操作,編譯器不會生成<clinit>()方法;
5.接口中不能使用靜態(tài)語句塊,但仍然有變量初始化賦值操作,所以接口會生成<clinit>()方法,但是,接口的<clinit>()不需要先執(zhí)行父接口的<clinit>()且實現(xiàn)類初始化時不會執(zhí)行接口的<clinit>()方法。
6.VM會保證一個類的<clinit>()方法在多線程環(huán)境中被正確地加鎖、同步。
總結(jié):
類加載過程分為:加載——驗證——準備——解析——初始化
加載:又分為三個階段:
(1)通過一個類的全限定名來獲取定義此類的二進制字節(jié)流;
(2)將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu);
(3)在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。
驗證 :確保Class文件的字節(jié)流中包含的信息符合當前虛擬機的要求
分為:文件格式驗證、元數(shù)據(jù)驗證、字節(jié)碼驗證、符號引用驗證
(1)文件格式驗證:保證字節(jié)流是符合Class文件的規(guī)范,能被當前VM處理,從而保證字節(jié)流能夠正確解析進入方法區(qū);
(2)元數(shù)據(jù)驗證:對元數(shù)據(jù)信息進行語義分析;
(3)字節(jié)碼驗證:對類的方法體進行校驗,對數(shù)據(jù)流和控制流分析;
(4)符號引用驗證:對類自身以外的信息進行匹配性校驗,保證解析階段能正常進行;
準備:正式為類變量分配內(nèi)存并設(shè)定初始值(數(shù)據(jù)的零值),內(nèi)存時在方法區(qū)中分配。(不是實例變量)
解析:將常量池內(nèi)的符號引用替換為直接引用,主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用點限定符7類符號引用進行解析。
初始化:真正開始執(zhí)行類的定義的Java程序代碼(字節(jié)碼),實際上是執(zhí)行類的構(gòu)造器<clinit>()方法的過程。(類變量的賦值以及靜態(tài)語句塊)