Java是平台無關性語言,而構成平台無關性的基石就是位元組碼。其實,虛擬機器並不關心class的來源是什麼語言,
只要它符合class檔案應有的結構就可以在java虛擬機器上運行
該檔案是一組以八位位元組為基本單位的二進位流,中間沒有任何分隔字元。其中常量池是class檔案結構中與其它項目關
聯最多的資料類型,也是佔用class檔案空間最大的項目之一
常量池之中主要存放兩類常量:
A, 字面量:如文本字串,被聲明為final的常量值
B, 符號引用:包括類和介面的全限定名,欄位的名稱和描述符,方法的名稱和描述符
當虛擬機器運行時需要從常量池獲得對應的符號引用,再在類建立是或運行時解析並翻譯
到具體的記憶體位址中
Java中天成可以動態擴充的語言特性就是依賴運行期間動態載入和動態連結這個特點實
現的,類在整個生命週期中包括了:載入,串連(驗證,準備,解析三個階段稱為串連),
初始化,使用和卸載七個階段
其中除瞭解析階段之外,其餘的階段順序是確定的,類的載入必須按照這種順序按部就
的“開始”,注意不是按部就班地進行或完成,因為這些階段通常都是相互交叉地混合式進
行的,通常會在一個階段執行的過程中調用或者啟用另外一個階段,而解析階段在某種情況
可以在初始化階段之後在開始,這樣做是為了支援java語言的運行時綁定,或者動態綁定
注意,虛擬機器規範嚴格規定了有且只有四種情況必須立即對類進行初始化:
A. 使用new關鍵字執行個體化對象的時候,讀取或者設定一個類的靜態欄位的時候
以及調用一個類的靜態方法的時候(被final修飾,已在編譯期間把結果放入調用類的常量池的靜態欄位除外,下面會有程式說明)
B. 當初始化一個類的時候,如果發現其父類沒有被初始化,那麼先要觸發其父類的初始化(但是一個介面初始化時,並不要求父介面全部都完成初始化,只有在真正使用父介面的時候才會初始化)
C. 使用java.lang.reflect包的方法對類進行反射調用的時候,如果沒有進行過初始化,則需要觸發其初始化
D. 當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含main方法的那個類),虛擬機器會先初始化這個類
這四種情境中的行為稱為對一個類進行主動引用,除此之外所有引用類的方式,都不會觸發初始化,稱為被動引用
注意,對於靜態地段,通過子類引用父類的靜態欄位,不會觸發子類的初始化
public class SuperClass {
static{
System.out.println("Super init");
}
public static int value = 123;
}
***************************
public class SubClass extends SuperClass{
static{
System.out.println("Sub init");
}
public static void main(String args[]){
System.out.print(SubClass.value);
}
}
對於該程式,輸出只會是Superinit,因為對於靜態欄位,只有直接定義這個欄位的類才會被初始化。
情景一:由於常量在編譯階段會存入調用類的常量池中,本質上沒有引用到定義常量的類,因此也不會觸發定義常量的類的初始化,看下面代碼
Publicclass A{
static{
System.out.println("ConstClass init");
}
publicstaticfinal String
str =
"Hello";
}
publicclass B {
publicstaticvoid main(String[] args){
System.out.println(A.str);
}
}
該段代碼輸出為Hello,雖然str定義在類A中,但是在編譯階段將此常量的值“hello”存入到調用類B的常量池中,對常量A.str的引用實際上轉換為B類對自身常量池的引用了
情景二:當然,下面代碼會先輸出ConstClass init
Hello
Public class A{
static{
System.out.println("ConstClass init");
}
publicstaticfinal String
str =
"Hello";
publicstaticvoid main(String[] args){
System.out.println(A.str);
}
}
A. 為什麼呢?因為現在類A中含有main方法,根據前面所說的四種必須初始化情況的情況D, 當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含main方法的那個類),虛擬機器會先初始化這個類,也就是說在調用main方法之前就已經將A初始化了,所以會輸出static語句塊的語句