標籤:等於 自己 .class 對比 結束 完成 資料結構 stc 動態綁定
參考如下兩篇並整理。
https://www.cnblogs.com/dongguacai/p/5860241.html
https://www.cnblogs.com/ITtangtang/p/3978102.html
類的生命週期是從被載入到虛擬機器記憶體中開始,到卸載出記憶體結束。過程共有七個階段。
1.載入---2.驗證---3.準備---3.解析---5.初始化---6.使用---7.卸載
載入---(連結)----驗證(校正、檢查)----準備----解析-----初始化----使用-----卸載
其中類載入的過程包括了載入、驗證、準備、解析、初始化五個階段。
在這五個階段中,載入、驗證、準備和初始化這四個階段發生的順序是確定的,而解析階段則不一定,它在某些情況下可以在初始化階段之後開始,這是為了支援Java語言的運行時綁定(也成為動態綁定或晚期綁定)。
另外注意這裡的幾個階段是按順序開始,而不是按順序進行或完成,因為這些階段通常都是互相交叉地混合進行的,通常在一個階段執行的過程中調用或啟用另一個階段。
【1.載入(裝載)】
在裝載階段,虛擬機器需要完成以下3件事情
(1) 通過一個類的全限定名來擷取定義此類的二進位位元組流。
(2) 將這個位元組流所代表的靜態儲存結構轉化為方法區的運行時資料結構。
(3) 在Java堆中產生一個代表這個類的java.lang.Class對象,作為方法區這些資料的訪問入口。
虛擬機器規範中並沒有準確說明二進位位元組流應該從哪裡擷取以及怎樣擷取,這裡可以通過定義自己的類載入器去控制位元組流的擷取方式。
【2.驗證(校正、檢查)】
虛擬機器如果不檢查輸入的位元組流,對其完全信任話,很可能會因為載入了有害的位元組流而導致系統奔潰。
1.檔案格式驗證:驗證位元組流是否符合Class檔案格式的規範;例如:是否以0xCAFEBABE開頭、主次版本號碼是否在當前虛擬機器的處理範圍之內、常量池中的常量是否有不被支援的類型。
2.中繼資料驗證:對位元組碼描述的資訊進行語義分析(注意:對比javac編譯階段的語義分析),以保證其描述的資訊符合Java語言規範的要求;例如:這個類是否有父類,除了java.lang.Object之外。
3.位元組碼驗證:通過資料流和控制流程分析,確定程式語義是合法的、符合邏輯的。
4.符號引用驗證:確保解析動作能正確執行。
驗證階段是非常重要的,但不是必須的,它對程式運行期沒有影響,如果所引用的類經過反覆驗證,
那麼可以考慮採用-Xverifynone參數來關閉大部分的類驗證措施,以縮短虛擬機器類載入的時間。
【3.準備】————為類的靜態變數分配記憶體,並將其初始化為預設值
這個階段正式為類變數(被static修飾的變數)分配記憶體並設定類變數初始值,這個記憶體配置是發生在方法區中。
1、注意這裡並沒有對執行個體變數進行記憶體配置,執行個體變數將會在對象執行個體化時隨著對象一起分配在JAVA堆中。
2、這裡設定的初始值,通常是指資料類型的‘“零”值。
public static int value = 123;
value在準備階段過後的初始值為0而不是123,而把value賦值的putstatic指令將在初始化階段才會被執行
【4.解析】
【5.初始化】
詳細見此
【1】在如下三個情境(能產生四個位元組碼指令的情境),如果類還未初始化,則初始化。
(4個指令【new、getstatic、putstatic、invokestatic】)
A.new
new Test();
B.讀取或設定類的靜態變數
int b=Test.a; Test.a=b;
C.調用靜態函數
Test.doSomething();
【2】反射調用。
Class.forName(“com.mengdd.Test”);
【3】子類初始化,要先初始化父類(所有父類)。
【4】虛擬機器啟動時,要初始化主類。直接使用java.exe命令來運行某個主類。
只有上述四種情況會觸發初始化,也稱為對一個類進行主動引用。
除此以外,有其他方式都不會觸發初始化,稱為被動引用。
注意:
(1)子類引用父類的靜態變數,不會導致子類初始化。
(2)通過數組定義引用類,不會觸發此類的初始化
(3)引用常量時,不會觸發該類的初始化
舉例:
(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 init123
(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]; }}
執行結果:
(3)引用常量時,不會觸發該類的初始化
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修飾某個類變數時,它的值在編譯時間就已經確定好放入常量池了,所以在訪問該類變數時,等於直接從常量池中擷取,並沒有初始化該類。
初始化的步驟
1、如果該類還沒有載入和串連,則程式先載入該類並串連。
2、如果該類的直接父類沒有載入,則先初始化其直接父類。
3、如果類中有初始化語句,則系統依次執行這些初始化語句。
在第二個步驟中,如果直接父類又有直接父類,則系統會再次重複這三個步驟來初始化這個父類,
依次類推,JVM最先初始化的總是java.lang.Object類。
當程式主動使用任何一個類時,系統會保證該類以及所有的父類都會被初始化。
【6.使用】
【7.卸載】
【Java】【JVM】類的生命週期【整理】