加載->驗證->準備->解析->初始化java
或者面試
加載->驗證->準備->初始化->解析數組
什麼狀況下須要開始類加載過程的第一個階段:加載?Java虛擬機規範中並無進行強制約束,這點能夠交給虛擬機的具體實現來自由把握。可是對於初始化階段,虛擬機規範則是嚴格規定了有且只有5種狀況必須當即對類進行初始化(而加載、驗證、準備天然須要在此以前開始): 有且僅有如下五種狀況:安全
這段文字完徹底全來自於《深刻理解 Java 虛擬機》,我想強調的有兩點:bash
一、以上五點是指類被初始化(參考初始化階段)的時機,而不是被加載的時機。數據結構
二、加載、驗證、準備這三個階段能夠先進行,而初始化能夠遲遲不進行直到必要的時候編輯器
比較容易理解,其實就是將 .class 文件加載到內存,並生成一個 Class 對象來方便管理,具體流程以下:佈局
簡化流程圖以下所示: spa
String[] strArr = new String[10];
複製代碼
這個語句會涉及到兩個類:翻譯
驗證是鏈接階段的第一步,這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。驗證大體上會完成下面四個階段的檢驗工做:
準備階段也比較簡單,搞清楚如下幾點便可:
public static int value=123;
只是將 value 賦值爲 0 而不是 123。注:各類Java 各類基本類型 0 值對照表以下
public static final int value=123;
則 value 在準備階段即被賦值爲 123,至於緣由也是顯而易見的
解析是將符號引用轉化成直接引用的階段。符號引用和直接引用在《深刻理解 Java 虛擬機》中的定義以下:
上面的定義看的一臉懵逼,我舉個栗子來理解這段文字。
public class Test {
public static final mian(String[] args) {
System.out.println("Hello, /word!"); //1
}
}
複製代碼
如上能夠看到 Test 是一個很是簡單的類,Test 類中調用了 System.out.pintln 接口向屏幕上輸出 Hello, world 。該類的 .class 文件中的字符串常量中(層層轉換後)確定會包含如下的一個常量:
invokevirtual java/io/PrintStream.println:(Ljava/lang/String;)V
複製代碼
很明顯能夠看出以上是一個方法的調用(即 System.out.println())指令:其中 java/io/PrintStream 是方法所在的類,println 是方法名,Ljava/lang/String 表示參數類型,這樣就能惟一肯定須要調用的方法,而java/io/PrintStream.println:(Ljava/lang/String;)V
即咱們的所說的符號引用。一樣若是訪問其餘類、接口、類的字段、類的方法、接口的方法等,都須要在 .class 文件中經過符號引用指定。因爲只是一個字符串,因此是虛擬機無關的。
Test 類的.class 文件最終是要被加載到虛擬機內存中才能被執行的,而在內存中方法的訪問是經過地址實現的。因此須要將符號引用java/io/PrintStream.println:(Ljava/lang/String;)V
轉換成一個指向方法在內存中具體地址(該方法所在的類一定完成了加載、驗證、準備和解析的階段)的指針,.class 中指令變成相似以下形式:
invokevirtual 0xfe1886d2;(配圖以下)
複製代碼
經過這個例子能夠簡單的理解符號引用和直接引用以及它們的關係,從而明白解析階段的做用。
類初始化階段是類加載過程的最後一步,該階段才真正開始執行類中定義的Java程序代碼(或者說是字節碼)。
類初始化過程即執行 <clinit>()方法的過程,而 <clinit>() 方法又是由編譯器自動收集類中的全部類變量的賦值動做和靜態語句塊(static{}塊)中的語句合併產生的,編譯器收集的順序是由語句在源文件中出現的順序所決定的。須要注意的是:
靜態語句塊中只能訪問到定義在靜態語句塊以前的變量,定義在它以後的變量,在前面的靜態語句塊能夠賦值,可是不能訪問。
初始化的時機在類加載加載時機一章節已經列出,是爲了區分類加載時機和類初始化時機。只有清晰的理解須要初始化的各類場景才能真正把握初始化的含義。
類的加載不少人似懂非懂,特別是解析和初始化:準備和初始化階段完成了 static 變量的內存分配和賦值以及 static 代碼塊中代碼的執行。出道網上的面試題,在不百度的狀況下寫出最後輸出的字符串,若是能正確寫出答案那大機率已經弄懂了解析和初始化階段,面試題以下:
class Grandpa {
static
{
System.out.println("爺爺在靜態代碼塊");
}
}
class Father extends Grandpa {
static
{
System.out.println("爸爸在靜態代碼塊");
}
public static int factor = 25;
public Father() {
System.out.println("我是爸爸~");
}
}
class Son extends Father {
static
{
System.out.println("兒子在靜態代碼塊");
}
public Son() {
System.out.println("我是兒子~");
}
}
public class InitializationDemo {
public static void main(String[] args) {
System.out.println("爸爸的歲數:" + Son.factor); //入口
}
}
複製代碼