這篇文章帶給大家的內容是關於JVM類別載入機制的詳細介紹(程式碼範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
Java不同於C/C 這類傳統的編譯型語言,也不同於php這一類動態的腳本語言。可以說Java是一種半編譯語言,我們寫的類別會先被編譯成.class文件,這個.class是一串二進位的位元組流。然後當要使用這個類別的時候,就會將這個類別對應的.class檔案載入進記憶體中。而將這個.class的內容載入進內存,正是透過Jvm類別載入機制實現的。
虛擬機器把描述類別的資料從class檔案載入到內存,並對資料進行校驗,轉換解析與初始化,最終形成可以被虛擬機器直接使用的Java類型,這就是虛擬機器的類載入機制。
載入
載入時「類別載入」過程的第一步,在載入過程中,虛擬機器需要完成以下三件事
透過一個類別的全限定名稱來取得定義此類的二進位位元組流。
將這個位元組流所代表的靜態儲存結構轉換為方法區的執行時間資料結構。
在記憶體中產生一個代表這個類別的java.lang.Class對象,作為方法區的這個類別的各種資料的存取入口。
值得一提的是,在載入階段既可以使用系統提供的引導類別載入器來完成,也可以由使用者自訂的類別載入器來完成,相對而是比較自由的,但對於數組則不是這樣了,數組類別本身不透過類別載入創建,它是由Java虛擬機器直接創建的。但資料所存放的元素類型是需要類別載入器去建立的。
載入階段與下一階段的連線部分是交叉進行的,但載入階段和連線階段的開始時間仍然會保持固定的先後順序。
驗證
驗證時連接階段的第一步,此階段的目的是為了確保Class檔案的位元組流中包含的資訊複合當前虛擬機的要求,並且不會危害虛擬機器本身的安全。雖然說數組越界,將物件胡亂轉型這些操作會被編譯器拒絕編譯,但.class檔案並不一定要求從Java原始碼編譯而來,可以從其他途徑產生,故而需要對.class檔案的二進位串流進行驗證。
驗證階段的重要性是不言而喻的,這個階段是否嚴謹,直接決定了Java虛擬機是否能承受惡意程式碼的攻擊,從執行效能的角度上講,驗證階段的工作量在虛擬機器的類別載入系統中又佔了相當大的一部分。
從整體來看,驗證階段大致可分成4部分的檢驗動作:檔案格式驗證,元資料驗證,字節碼驗證,符號引用驗證。
符號驗證:主要目的是確保輸入的位元組流能正確地解析並儲存於方法區之內,格式上符合描述一個Java類型資訊的要求。這一部分是基於二進位流驗證的,之後會載入到記憶體中,後續驗證是在記憶體中驗證。
元資料驗證:此驗證主要是對類別的元資料資訊進行語意校驗,保證不存在不符合Java語言規範的元資料資訊。
字節碼驗證:這一部分是驗證階段中最複雜的一階段,主要目的是透過資料流和控制流程分析,確定程式是合法的,符合邏輯的。
符號引用驗證:符號引用是發生在虛擬機器將符號參考轉換為直接引用的時候,目的是卻好解析動作能正常執行。
準備
準備階段是為正式類別變數(靜態變數)分配記憶體並設定類別變數初始值的階段,這些變數所所使用的記憶體都講在方法區中進行分配的。值得一提的是,這時候進行分配的僅為類別變數(靜態變數),而不包括實例變數。
通常情況下,設定類別變數初始值,而這個初始值指的是資料型別的預設值,例如int型則是0。但若類別變數被final修飾,則情況又不一樣,那樣的話會直接對給定值進行賦值。
解析
解析階段是虛擬機器將常數池內的符號參考替換為直接引用的過程。這裡解釋以下什麼是符號引用,什麼是直接引用。
符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義得定位到目標即可。
直接引用:直接引用可以是指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。
解析動作主要針對類別或接口,字段,類別方法,介面方法,方法類型,方法句柄和呼叫點限定符7類符號引用進行。
初始化
#類別初始化階段是類別載入過程的最後一步,前面的類別載入過程,除了在載入階段使用者應用程式可以透過自訂類別載入器參與之外,其餘動作完全由虛擬機器主導和控制。到了初始化階段,才會真正開始執行類別中定義的Java程式碼。
在準備階段,變數已經賦過一次系統要求的初始值,而在初始化階段,則根據程式設計師透過程式製定的計畫區初始化類別變數和其他資源。
public class StaticTest { public static void main(String[] args) { staticFunction(); } static StaticTest st = new StaticTest(); static { System.out.println("1"); } { System.out.println("2"); } StaticTest() { System.out.println("3"); System.out.println("a="+a+",b="+b); } public static void staticFunction(){ System.out.println("4"); } int a=110; static int b =112; }
這段程式碼的運行結果是什麼呢?
答案是:
2 3 a=110,b=0 1 4
這是為什麼呢,大家不妨思考以下。
理解這段程式碼不光是要明白Java的類別載入機制,還要明白初始化階段,靜態程式碼區塊與靜態成員變數的初始化順是與程式碼順序有關的。
類別載入的過程是:裝載–>連接(驗證,準備,解析)–>初始化。
1.在準備階段,會為類別變數設定預設值,所以在案例一中:st=null,b=0,
2.在初始化階段,會先執行類建構器,
換句話說,就是執行static修飾的程式碼區塊和為static修飾的變數賦值而已。而static修飾的程式碼區塊和類別變數的執行順序是按照它在檔案中的先後順序執行的。而static StaticTest st = new StaticTest()排在第一,所以會執行new StaticTest(),也就是進行物件的初始化
2.1.在物件的初始化過程中,會先執行成員變數(程式碼區塊),然後再執行建構方法.成員變數的執行順序也是誰先宣告,誰先執行,所以排在第一的程式碼區塊
2.2成員變數執行完後,執行建構方法.此時,a=110,b=0;
3.由static StaticTest st = new StaticTest();觸發的非靜態程式碼的初始化過程到此結束,接下來繼續執行靜態程式碼的初始化,於是輸出1 。
4.整個類別載入到此結束,執行程式碼,輸出 4 。
再看下一道
public class StaticTest { public static void main(String[] args) { staticFunction(); } static { System.out.println("1"); } { System.out.println("2"); } StaticTest() { System.out.println("3"); System.out.println("a="+a+",b="+b); } public static void staticFunction(){ System.out.println("4"); } int a=110; static int b =112; static StaticTest st = new StaticTest(); //将这条语句放到最下面 }
只是改變一條語句,而這段程式碼的運行結果是
1 2 3 a=110,b=112 4
以上是JVM類別載入機制的詳細介紹(程式碼範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!