JAVA原始碼編譯由三個流程組成:
1、原始碼編譯機制。
2、類別載入機制
3、類別執行機制
我們這裡主要介紹編譯和類別載入這兩種機制。
一、原始碼編譯
程式碼編譯由JAVA原始碼編譯器來完成。主要是將原始碼編譯成字節碼檔(class檔)。字節碼檔案格式主要分為兩部分:常數池和方法字節碼。
二、類別載入
類別的生命週期是從載入到虛擬機器記憶體開始,到卸載出記憶體結束。過程共有七個階段,其中到初始化之前的都是屬於類別載入的部分
載入----驗證----準備----解析-----初始化----使用--- --卸載
系統可能在第一次使用某個類別時載入該類,也可能採用預先載入機制來載入某個類,當執行某個java程式時,會啟動一個java虛擬機進程,兩次執行的java程式處於兩個不同的JVM進程中,兩個jvm之間並不會共享資料。
1、載入階段
這個流程中的載入是類別載入機制中的一個階段,這兩個概念不要混淆,這個階段需要完成的事情有:
1)透過一個類別的全限定名來獲取定義此類別的二進位位元組流。
2)將這個位元組流所代表的靜態儲存結構轉換為方法區的運行時資料結構。
3)在java堆中產生一個代表這個類別的Class對象,作為存取方法區中這些資料的入口。
由於第一點沒有指明從哪裡獲取以及怎樣獲取類的二進製字節流,所以這一塊區域留給我開發者很大的發揮空間。這個我在後面的類別載入器中在進行介紹。
2、準備階段
這個階段正式為類別變數(被static修飾的變數)分配記憶體並設定類別變數初始值,而這個記憶體分配是發生在方法區。
1、注意這裡並沒有對實例變數進行記憶體分配,實例變數將會在物件實例化時隨著物件一起分配在JAVA堆中。
2、這裡設定的初始值,通常是指資料型別的零值。
private static int a = 3;
這個類別變數a在準備階段後的值是0,將3賦值給變數a是發生在初始化階段。
3、初始化階段
初始化是類別載入機制的最後一步,這時候才正真開始執行類別中定義的JAVA程式碼。在前面準備階段,類別變數已經賦過一次系統需求的初始值,在初始化階段最重要的事情就是對類別變數進行初始化,關注的重點是父子類別之間各類別資源初始化的順序。
java類別中對類別變數指定初始值有兩種方式:1、宣告類別變數時指定初始值;2、使用靜態初始化區塊為類別變數指定初始值。
初始化的時機
1)創建類別實例的時候,分別有:1、使用new關鍵字創建實例;2、透過反射創建實例;3、透過反序列化方式創建實例。
new Test(); Class.forName(“com.mengdd.Test”);
2)呼叫某個類別的類別方法(靜態方法)
Test.doSomething();
3)存取某個類別或介面的類別變量,或為此類別變數賦值。
int b=Test.a; Test.a=b;
4)初始化某一類別的子類別。當初始化子類別的時候,該子類別的所有父類別都會被初始化。
5)直接使用java.exe指令來執行某個主類別。
除了上面幾種方式會自動初始化一個類,其他訪問類的方式都稱不會觸發類的初始化,稱為被動引用。
1、子類別引用父類別的靜態變量,不會導致子類別初始化。
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
2、透過陣列定義引用類,不會觸發此類的初始化
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); } }
用final修飾某個類別變數時,它的值在編譯時就已經確定好放入常數池了,所以在存取該變數時,等於直接從常數池中獲取,並沒有初始化該類別。
初始化的步驟