jvm - 類的編譯,提到了把本地機器碼轉變成字節碼,以及編譯的優化。那這些字節碼文件是怎麼到JVM的呢?
一個類從被加載到虛擬機內存中開始,到卸載出內存,它的整個生命週期包括:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個階段。
在Java虛擬機中類加載的全過程,包括加載、驗證、準備、解析和初始化這5個階段所執行的具體動做,這些都是有類加載器來實現的。java
加載是類加載過程的一個階段。首先來一個簡單的代碼,打印###以及建立一個Hello對象。安全
public class ClassLoad { public static void main(String[] args) { System.out.println("########################"); Hello hello = new Hello(); } }
運行以前,設置-XX:+TraceClassLoading
運行結果以下(截取後面部分),能夠看到com.jvm.load.ClassLoad
先被加載,而後是com.jvm.cls.Hello
。ClassLoad是這個main方法的主類,因此優先加載。Hello的加載,是在實例化的時候,也就是被用到的時候,若是讀者本身去斷點,那就更直觀的看到了。
上面這個圖,能夠看到輸出了類的全限定名,類加載器就是經過這個來獲取它的二進制字節流,這個二進制字節流來源以下:網絡
驗證是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。當加載的class文件不符合虛擬機的要求,java虛擬機是沒法執行這個字節碼的,因此要先看看有沒有符合,符合了纔給虛擬機執行後續操做。
jvm
準備是正式爲類變量分配內存並設置類變量初始值的階段。也就是說com.jvm.load.ClassLoad
和com.jvm.cls.Hello
在虛擬機中的內存分配是在這個階段。這時候進行內存分配的僅包括類變量(被static修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨着對象一塊兒分配在Java堆中。設置類變量初始值一般狀況下就是數據類型的零值。優化
// 準備階段value=0 public static int value = 123; // 準備階段value2=123 public static final int value2 = 123;
解析是虛擬機將常量池內的符號引用替換爲直接引用的過程。
好比com.jvm.load.ClassLoad
編譯的時候,不知道com.jvm.cls.Hello
的實際內存地址,此時用符號引用,當com.jvm.cls.Hello
加載到內存後,此時就改成直接引用,指向Hello的內存位置。
spa
在準備階段value=0,在初始化階段,value才賦值爲123。
類初始化的條件:代理
下面看看類雖然被加載,卻沒有初始化的例子。
SuperClass:code
public class SuperClass { static { System.out.println("SuperClass init"); } public static int value = 123; }
SubClass:對象
public class SubClass extends SuperClass { static { System.out.println("SubClass init"); } }
ClassLoad:blog
public class ClassLoad { public static void main(String[] args) { System.out.println("########################"); //Hello hello = new Hello(); System.out.println(SubClass.value); } }
運行結果以下:
能夠看到SubClass被加載了,可是並無輸出SubClass init
。
類加載器有這幾個:
下圖展現了類加載器直接的層次關係,成爲類加載器的雙親委派模型。雙親委派模型要求除了頂層的啓動類加載器外,其他的類加載器都應當有本身的父類加載器。
他的工做過程是這樣的:
雙親委派模型一個顯而易見的好處就是Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係。