「代碼編譯的結果從本地機器碼轉變爲字節碼,是存儲格式發展的一小步,倒是變成語言發展的一大步」,這句話出自《深刻理解JAVA虛擬機》一書,後面關於jvm的系列文章主要都是參考這本書。java
JAVA源碼編譯由三個過程組成:數組
一、源碼編譯機制。數據結構
二、類加載機制jvm
三、類執行機制code
咱們這裏主要介紹編譯和類加載這兩種機制。對象
1、源碼編譯
代碼編譯由JAVA源碼編譯器來完成。主要是將源碼編譯成字節碼文件(class文件)。字節碼文件格式主要分爲兩部分:常量池和方法字節碼。blog
2、類加載
類的生命週期是從被加載到虛擬機內存中開始,到卸載出內存結束。過程共有七個階段,其中到初始化以前的都是屬於類加載的部分接口
加載----驗證----準備----解析-----初始化----使用-----卸載生命週期
系統可能在第一次使用某個類時加載該類,也可能採用預加載機制來加載某個類,當運行某個java程序時,會啓動一個java虛擬機進程,兩次運行的java程序處於兩個不一樣的JVM進程中,兩個jvm之間並不會共享數據。進程
一、加載階段
這個流程中的加載是類加載機制中的一個階段,這兩個概念不要混淆,這個階段須要完成的事情有:
1)經過一個類的全限定名來獲取定義此類的二進制字節流。
2)將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。
3)在java堆中生成一個表明這個類的Class對象,做爲訪問方法區中這些數據的入口。
因爲第一點沒有指明從哪裏獲取以及怎樣獲取類的二進制字節流,因此這一塊區域留給我開發者很大的發揮空間。這個我在後面的類加載器中在進行介紹。
二、準備階段
這個階段正式爲類變量(被static修飾的變量)分配內存並設置類變量初始值,這個內存分配是發生在方法區中。
一、注意這裏並無對實例變量進行內存分配,實例變量將會在對象實例化時隨着對象一塊兒分配在JAVA堆中。
二、這裏設置的初始值,一般是指數據類型的零值。
private static int a = 3;
這個類變量a在準備階段後的值是0,將3賦值給變量a是發生在初始化階段。
三、初始化階段
初始化是類加載機制的最後一步,這個時候才正真開始執行類中定義的JAVA程序代碼。在前面準備階段,類變量已經賦過一次系統要求的初始值,在初始化階段最重要的事情就是對類變量進行初始化,關注的重點是父子類之間各種資源初始化的順序。
java類中對類變量指定初始值有兩種方式:一、聲明類變量時指定初始值;二、使用靜態初始化塊爲類變量指定初始值。
初始化的時機
1)建立類實例的時候,分別有:一、使用new關鍵字建立實例;二、經過反射建立實例;三、經過反序列化方式建立實例。
new Test();
Class.forName(「com.mengdd.Test」);
2)調用某個類的類方法(靜態方法)
Test.doSomething();
3)訪問某個類或接口的類變量,或爲該類變量賦值。
int b=Test.a; Test.a=b;
4)初始化某個類的子類。當初始化子類的時候,該子類的全部父類都會被初始化。
5)直接使用java.exe命令來運行某個主類。
除了上面幾種方式會自動初始化一個類,其餘訪問類的方式都稱不會觸發類的初始化,稱爲被動引用。
一、子類引用父類的靜態變量,不會致使子類初始化。
public class SupClass { public static int a = 123;
static {
System.out.println("supclass init");
}
}
public class SubClass extends SupClass {
static {
System.out.println("subclass init");
}
} public class Test {
public static void main(String[] args) {
System.out.println(SubClass.a);
}
}
執行結果:
supclass init 123
二、經過數組定義引用類,不會觸發此類的初始化
public class SupClass { public static int a = 123;
static {
System.out.println("supclass init");
}
}
public class Test {
public static void main(String[] args) {
SupClass[] spc = new SupClass[10];
}
}
執行結果:
三、引用常量時,不會觸發該類的初始化
public class ConstClass {
public static final String A= "MIGU";
static { System.out.println("ConstCLass init");
}
}
public class TestMain { public static void main(String[] args) {
System.out.println(ConstClass.A);
}
}
執行結果:
MIGU
用final修飾某個類變量時,它的值在編譯時就已經肯定好放入常量池了,因此在訪問該類變量時,等於直接從常量池中獲取,並無初始化該類。
初始化的步驟
一、若是該類尚未加載和鏈接,則程序先加載該類並鏈接。
二、若是該類的直接父類沒有加載,則先初始化其直接父類。
三、若是類中有初始化語句,則系統依次執行這些初始化語句。
在第二個步驟中,若是直接父類又有直接父類,則系統會再次重複這三個步驟來初始化這個父類,依次類推,JVM最早初始化的老是java.lang.Object類。當程序主動使用任何一個類時,系統會保證該類以及全部的父類都會被初始化。