前言:
在Java編程中,類的生命周期是指類從被加載到內存中開始,到被卸載出內存為止的整個過程。了解類的生命周期對于理解Java程序的運行機制以及性能優化非常重要。本文會深入探尋類的生命周期,讓讀者對此有深刻印象。
目錄
?編輯前言:
類的生命周期
類的加載階段
核心任務
連接階段
?驗證(Verification)
?準備(Preparation)
解析(Resolution)
初始化階段
不會導致初始化
初始化觸發條件(嚴格規定)
使用階段
卸載階段
面試題
總結
類的生命周期
一個類的生命周期分為五個階段,分別是加載,連接,初始化,使用和卸載。
類的加載階段
加載是將類的字節碼文件(.class)加載到 JVM 內存的過程,類加載器會通過不同的形式,去得到字節碼文件的內容,然后通過JVM,把內容加載到方法區和堆區,而我們開發者只能訪問到堆區的內容,方法區我們是訪問不到的,堆區的內容是字節碼文件的核心內容。
核心任務
-
查找字節碼:
-
通過類的全限定名查找字節碼文件
-
搜索路徑:類路徑(classpath)、JAR 文件、網絡資源等
-
-
創建類結構:
-
解析字節碼并創建對應的類數據結構
-
在方法區(元空間)存儲類的運行時常量池、字段和方法信息
-
-
創建 Class 對象:
-
在堆內存中創建 java.lang.Class 對象
-
該對象作為訪問類元數據的入口
-
連接階段
在加載階段結束后進入連接階段,連接階段分為三個子階段,確保類字節碼的正確性和可用性
?驗證(Verification)
確保字節碼符合 JVM 規范和安全要求:
-
文件格式驗證:魔數(0xCAFEBABE)、版本號等
-
元數據驗證:語義檢查(是否有父類、final類是否被繼承等)
-
字節碼驗證:數據流和控制流分析
-
符號引用驗證:檢查引用的類、字段和方法是否存在
?準備(Preparation)
為類變量分配內存并設置初始值:
-
靜態變量分配:
-
在方法區分配內存
-
設置類型默認值(零值)
-
int → 0
-
boolean → false
-
引用類型 → null
-
-
-
特殊處理:
-
static final 常量:直接賦程序指定值
-
// 準備階段直接賦值
public static final int MAX = 100;
解析(Resolution)
將符號引用轉換為直接引用:
-
符號引用:用一組符號描述引用的目標
-
直接引用:指向目標的指針、偏移量或句柄
初始化階段
執行靜態代碼塊的內容和給靜態變量賦值,以字節碼文件視角就是執行<clinit>方法
<clinit>()
?方法特性
-
自動生成:
-
編譯器收集所有類變量賦值和靜態代碼塊
-
按源代碼順序合并生成
-
-
線程安全:
-
JVM 保證只有一個線程執行初始化
-
其他線程會阻塞等待完成
-
-
父類優先:
-
執行子類的?
<clinit>()
?前 -
必須先執行父類的?
<clinit>()
-
不會導致初始化
- 無靜態代碼塊且無靜態變量賦值語句。
- 有靜態變量的聲明,但是沒有賦值語句。
- 靜態變量的定義使用final關鍵字,這類變量會在準備階段直接初始化?。
- 直接訪問父類的靜態變量,不會觸發子類的初始化。
- 數組的創建不會導致數組中元素的類進行初始化。
初始化觸發條件(嚴格規定)
new
?實例化對象訪問類的靜態變量(非 final 常量)
調用類的靜態方法
反射調用(Class.forName())
初始化子類時(父類需先初始化)
JVM 啟動時的主類
MethodHandle 解析結果涉及靜態方法
使用階段
類的初始化之后,它就可以在程序中自由使用了。這包括創建實例、調用方法和訪問字段等操作。在這個階段,對象會被創建和操作,它們各自也會經歷自己的生命周期。
卸載階段
?在某些情況下,當一個類不再需要時,它會被卸載。類的卸載發生在垃圾收集的過程中,當確定某個類的Class對象不再被引用,且對應的ClassLoader實例也不再存在時,JVM就可能卸載這個類。但是,在常見的Java應用中,由于系統類加載器加載的類一直會被引用,所以這些類通常只有在JVM停止運行時才會被卸載。
面試題
說出一下代碼的運行結果:
public class Demo01 {public static void main(String[] args) {System.out.println(B02.a);}
}
class A02
{static int a=0;static{System.out.println("A02");a=1;}
}
class B02 extends A02{static{System.out.println("B02");a=2;}
}
運行結果為A02 1? 原因是因為:訪問父類的靜態變量,只初始化父類。
public class Demo01 {public static void main(String[] args) {new B02();System.out.println(B02.a);}
}
class A02
{static int a=0;static{System.out.println("A02");a=1;}
}
class B02 extends A02{static{System.out.println("B02");a=2;}
}
首先執行main方法,new了B02所以要初始化它,但是它是子類,所以要先初始化A02,首先給賦值為0然后打印出A02,在給a賦值1,在初始化B02,打印出B02,a賦值為2,然后打印出a的值2
運行結果是:A02 B02 2
public class test5 {public static void main(String[] args) {System.out.println("A");new test5();new test5();}public test5(){System.out.println("B");}{System.out.println("C");}static {System.out.println("D");}}
- 先執行test5這個類的靜態代碼塊輸出D
- 在執行main方法打印A
- 在初始化test5,執行構造器方法和代碼塊方法,但是代碼塊先執行所以第一個new執行CB
- 第二個new執行CB
- 結果:DACBCB
以上面試題你都做對了嗎?
總結
? ? Java類的生命周期包括:加載(將字節碼加載到內存,生成Class對象)、連接(驗證字節碼、準備靜態變量內存并賦默認值、解析符號引用)、初始化(執行靜態代碼塊和賦值)、使用(創建實例、調用方法)、卸載(類不再使用時被回收)。按此順序完成從加載到銷毀的完整過程。
學習類的生命周期,能讓我們對java語言有一個更深的認識,也能讓我們在面試中多一點機會。
總之,類的生命周期從加載到卸載,經歷了多個階段,每個階段都有特定的任務和目標。理解類的生命周期有助于我們更好地理解和管理Java程序的運行機制。
感謝你的閱讀,希望文章能給你帶來幫助,你的閱讀點贊就是我最大的動力。