1.1 加載
加載的過程中,jvm要完成以下三件事:
- 通過一個類的全限定名來獲取定義此類的二進制字節(jié)流穿肄。
- 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)年局。
- 在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口咸产。
注意:對于第一條,jvm并沒有特殊地去指定這個類應(yīng)該從哪里獲取,基于這一條,現(xiàn)在的java平臺發(fā)展出了jar,war等;以及動態(tài)的運行時計算生成,也就是我們現(xiàn)在著名的動態(tài)代理技術(shù);另外也諸如由其他的文件生成,例如jsp矢否。
非數(shù)組類的加載階段:類的加載階段可以使用系統(tǒng)的類加載器,也可以自定義類加載器(重寫一個類加載器的loadClass()方法)。
數(shù)組類的加載階段:數(shù)組類本身不通過類加載器創(chuàng)建脑溢,它是由Java虛擬機直接創(chuàng)建的僵朗。一個數(shù)組類創(chuàng)建過程遵循以下規(guī)則:
- 如果數(shù)組的組件類型是引用類型,就遞歸采用上面的加載過程去加載這個組件類型屑彻,數(shù)組將在加載該組件類型的類加載器的類名稱空間上被標識验庙。
- 如果數(shù)組的組件類型不是引用類型(例如int[]數(shù)組),Java虛擬機將會把數(shù)組標記為與引導(dǎo)類加載器關(guān)聯(lián)酱酬。
- 數(shù)組類的可見性與它的組件類型的可見性一致壶谒,如果組件類型不是引用類型,那數(shù)組類的可見性將默認為public膳沽。
加載的階段完成后汗菜,jvm外部的二進制字節(jié)流按虛擬機所需要的格式存在方法區(qū)里让禀,方法區(qū)中的數(shù)據(jù)存儲格式由jvm自行定義。然后在內(nèi)存中實例化一個java.lang.Class類的對象(沒有明確規(guī)定是在java堆里,對于Hotspot來說陨界,Class對象比較特殊,雖然是對象巡揍,卻會放在方法區(qū)里)。
1.2 驗證
驗證是連接階段的第一步菌瘪,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當前虛擬機的要求腮敌,并且不會危害虛擬機自身的安全。
驗證階段大致上會完成下面4個階段的檢驗動作:
- 文件格式驗證:驗證字節(jié)流是否符合Class文件格式的規(guī)范俏扩,并且能被當前版本的虛擬機處理糜工。這階段的驗證是基于二進制字節(jié)流進行的,只有通過了這個階段的驗證后录淡,字節(jié)流才會進入內(nèi)存的方法區(qū)中進行存儲捌木,所以后面的3個驗證階段全部是基于方法區(qū)的存儲結(jié)構(gòu)進行的,不會再直接操作字節(jié)流嫉戚。
- 元數(shù)據(jù)驗證:對字節(jié)碼描述的信息進行語義分析刨裆,以保證其描述的信息符合Java語言規(guī)范的要求。(這個類是否有父類彬檀,父類是否繼承了不允許被繼承的final類等)帆啃。
- 字節(jié)碼驗證:通過數(shù)據(jù)流和控制流分析,確定程序語義是合法的窍帝、符合邏輯的努潘。保證被校驗類的方法體在運行時不會做出危害虛擬機安全的事件。
- 符號引用驗證:對類自身以外(常量池中的各種符號引用)的信息進行匹配性校驗坤学。
1.3 準備
準備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段慈俯,這些變量所使用的內(nèi)存都將在方法區(qū)中進行分配。注意:這時候進行內(nèi)存分配的僅包括類變量(被static修飾的變量)拥峦,而不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在Java堆中卖子。
我們這里舉個例子public static int a = 123;,這里的變量a就是類變量略号。并且這里我們需要注意一個事情,對于準備階段而已洋闽,變量a的值是0玄柠,而復(fù)制變量a為123的操作是在初始化階段的clinit方法中執(zhí)行的。
但是對于上面的情況诫舅,如果把變量a用final修飾,public static final int a = 123;羽利,那么在編譯時javac會為變量a生成ConstantValue屬性,在準備階段Jvm就會根據(jù)ConstantValue的設(shè)置為變量a賦值刊懈。
1.4 解析
解析階段是虛擬機將常量池內(nèi)的符號引用替換為直接引用的過程这弧。
符號引用:以一組符號來描述所引用的目標娃闲,符號可以是類和形式的字面量,只要使用時能無歧義的定位到目標即可匾浪,與虛擬機實現(xiàn)的內(nèi)存布局無關(guān)皇帮,引用的目標不一定已經(jīng)加載到內(nèi)存中。
直接引用:可以是直接指向目標的指針蛋辈、相對偏移量或者是一個能間接定位到目標的句柄属拾。直接引用是和虛擬機實現(xiàn)的內(nèi)存布局相關(guān)的,引用的目標必定已經(jīng)在內(nèi)存中存在冷溶。
除了invokeDynamic指令以外渐白,虛擬機實現(xiàn)可以對第一次解析的結(jié)果進行緩存(在運行時常量池中記錄直接引用,并把常量標示為已解狀態(tài))從而避免解析動作重復(fù)進行逞频。無論是否真正被執(zhí)行了多少次解析纯衍,jvm需要保證的是在同一個實體中,如果一個符號引用之前已經(jīng)被成功解析過虏劲,那后續(xù)的引用解析請求就應(yīng)該一直成功托酸;同理,第一次解析失敗了柒巫,那么其他指令對這個符號的解析請求也就應(yīng)該收到相同的異常励堡。
對應(yīng)上面的規(guī)則,invokeDynamic指令是例外的堡掏,invokeDynamic指令只有在程序?qū)嶋H運行到這條指令解析動作才開始進行应结。
而java的解析階段主要是針對于4塊:類或者接口的解析、字段解析泉唁、類方法解析鹅龄,接口方法解析