首先先來看幾個問題java
接下來看看jvm加載class文件的概述:程序員
jvm把描述類的數據從class文件加載到內存,並對數據進行校驗,轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。這句話差很少已經回答上面三個問題的大部分了。數組
與那些在編譯是須要進行鏈接工做的語言不一樣,在Java語言裏面,類型的加載和鏈接過程都是在程序運行期間完成的,這樣會在類加載是稍微增長一些性能開銷,可是卻能爲Java應用程序提供高度的靈活性,Java中能夠動態的擴展的語言特性就是依賴運行期動態加載和動態鏈接這個特色實現的。好比編寫一個使用接口的應用程序,能夠等到運行時在指定其實際的實現。這種組裝應用程序的方式普遍應用於Java程序之中。數據結構
類從被加載到jvm內存中開始,到卸載出內存爲止,它的生命週期包括了一下步驟:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Useing)和卸載(Unloading)七個階段。其中的驗證、準備和解析三個部分統稱爲連接(Linking),這七個階段的發生順序以下圖,注意是發生的順序,不是執行完成的前後順序。jvm
加載階段是「類加載」過程的一個階段,虛擬機須要作如下三件事:佈局
加載階段完成後,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,方法區中的數據存儲格式有虛擬機實現自定義,虛擬機規範未規定此區域的具體數據結構。而後再Java堆中實例化一個java.lang.Class類的對象,這個對象做爲程序訪問方法區中的這些類型數據的外部接口。加載階段與鏈接階段的部份內容是交替進行的,加載階段還沒有完成,鏈接階段可能已經開始,但這些夾在加載階段之中進行的動做,仍然屬於鏈接階段的內容,這兩個階段的開始時間仍然保持着固定的前後順序。性能
驗證階段虛擬機作了下面這些事情編碼
一、文件格式驗證spa
第一階段是要驗證字節流是否符合Class文件格式的規範,而且能被當前版本的虛擬機處理。會驗證一下這些內容。指針
二、元數據的驗證
三、字節碼的驗證
這個階段是驗證最爲複雜的一個階段,主要工做是進行數據流和控制流分析,緊接第二階段。
四、符號引用驗證
準備階段是正式爲類變量分配內存並設置類變量初始值的階段,注意是初始值不是最終變量的值,都將在方法區中進行分配。若是該變量不是靜態變量,將不會進行內存分配,而是會在類出乎實話的時候隨着對象一塊兒分配到Java堆中。另外這裏的初始值一般狀況下是零值。具體的初始化的值見下圖,圖片來源於《深刻理解Jvm虛擬機》。
解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程。
符號引用:符號引用以一組符號來描述所引用的目標,符號可使任何形式的字面量,只要使用時能無歧義地定位到目標便可。符號引用於虛擬機實現內存佈局無關,引用的目標不必定已經加載到內存中。
類初始化階段是類加載過程的最後一步,前面的類加載過程當中,除了在加載階段用戶應用程序能夠經過自定義類加載器參與以外,其他動做徹底有虛擬機主導和空值。到了初始化階段,才真正開始執行類中定義的Java程序代碼。
前面講到在準備階段變量已經富餘過一次初始值,而在初始化階段,則是根據程序員經過程序制定主觀計劃去初始化變量和其餘資源。
一下四中狀況會必須當即對類進行「初始化」。
除了上面4中場景,都不會觸發初始化,稱爲被動引用。
場景一
public class SupClass { public static int value = 100; static{ System.out.println("SupClass init..."); } } public class SubClass extends SupClass { static{ System.out.println("SubClass init..."); } }
客戶端代碼
public class InitTest { public static void main(String[] args) { System.out.println(SubClass.value); } }
輸出以下
SupClass init...
100
能夠看到經過子類引用父類的靜態字段,不會致使子類初始化。
場景二
其餘代碼同場景一,客戶端代碼變成以下
public class InitTest { public static void main(String[] args) { SupClass[] sca = new SupClass[10]; } }
這段代碼不會輸出任何結果。由於經過數組定義來引用類,不會觸發此類的初始化。
場景三
public class ConstClass { public static final String HELLO = "hello"; static{ System.out.println("ConstClass init..."); } }
客戶端代碼
public class InitTest { public static void main(String[] args) { System.out.println(ConstClass.HELLO); } }
輸出以下
hello
能夠看到常量在編譯階段會存入調用類的常量池中,本質上沒有直接引用到定義常量的類,所以不會觸發定義常量的類的初始化。
本篇文章依據如下兩點