版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/qq_34173549/article/details/79612705
類的生命周期
一個類從加載進記憶體到解除安裝出記憶體為止,一共經曆7個階段:
加載——>驗證——>準備——>解析——>初始化——>使用——>解除安裝
其中,類加載包括5個階段:
加載——>驗證——>準備——>解析——>初始化
在類加載的過程中,以下3個過程稱為連接配接:
驗證——>準備——>解析
是以,JVM的類加載過程也可以概括為3個過程:
加載——>連接配接——>初始化
C/C++在運作前需要完成預處理、編譯、彙編、連結;而在Java中,類加載(加載、連接配接、初始化)是在程式運作期間完成的。
在程式運作期間進行類加載會稍微增加程式的開銷,但随之會帶來更大的好處——提高程式的靈活性。Java語言的靈活性展現在它可以在運作期間動态擴充,所謂動态擴充就是在運作期間動态加載和動态連接配接。
類加載的時機
1. 類加載過程中每個步驟的順序
我們已經知道,類加載的過程包括:加載、連接配接、初始化,連接配接又分為:驗證、準備、解析,是以說類加載一共分為5步:加載、驗證、準備、解析、初始化。
其中加載、驗證、準備、初始化的開始順序是依次進行的,這些步驟開始之後的過程可能會有重疊。
而解析過程會發生在初始化過程中。
2. 類加載過程中“初始化”開始的時機
JVM規範中隻定義了類加載過程中初始化過程開始的時機,加載、連接配接過程都應該在初始化之前開始(解析除外),這些過程具體在何時開始,JVM規範并沒有定義,不同的虛拟機可以根據具體的需求自定義。
初始化開始的時機:
- 在運作過程中遇到如下位元組碼指令時,如果類尚未初始化,那就要進行初始化:new、getstatic、putstatic、invokestatic。這四個指令對應的Java代碼場景是:
- 通過new建立對象;
- 讀取、設定一個類的靜态成員變量(不包括final修飾的靜态變量);
- 調用一個類的靜态成員函數。
- 使用java.lang.reflect進行反射調用的時候,如果類沒有初始化,那就需要初始化;
- 當初始化一個類的時候,若其父類尚未初始化,那就先要讓其父類初始化,然後再初始化本類;
- 當虛拟機啟動時,虛拟機會首先初始化帶有main方法的類,即主類;
3. 主動引用 與 被動引用
JVM規範中要求在程式運作過程中,“當且僅當”出現上述4個條件之一的情況才會初始化一個類。如果間接滿足上述初始化條件是不會初始化類的。
其中,直接滿足上述初始化條件的情況叫做主動引用;間接滿足上述初始化過程的情況叫做被動引用。
那麼,隻有當程式在運作過程中滿足主動引用的時候才會初始化一個類,若滿足被動引用就不會初始化一個類。
4. 被動引用的場景示例
- 示例一
public class Fu{
public static String name = "柴毛毛";
static{
System.out.println("父類被初始化!");
}
}
public class Zi{
static{
System.out.println("子類被初始化!");
}
}
public static void main(String[] args){
System.out.println(Zi.name);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
輸出結果:
父類被初始化!
柴毛毛
原因分析:
本示例看似滿足初始化時機的第一條:當要擷取某一個類的靜态成員變量的時候如果該類尚未初始化,則對該類進行初始化。
但由于這個靜态成員變量屬于Fu類,Zi類隻是間接調用Fu類中的靜态成員變量,是以Zi類調用name屬性屬于間接引用,而Fu類調用name屬性屬于直接引用,由于JVM隻初始化直接引用的類,是以隻有Fu類被初始化。
- 示例二
public class A{
public static void main(String[] args){
Fu[] arr = new Fu[10];
}
}
并沒有輸出“父類被初始化!”
這個過程看似滿足初始化時機的第一條:遇到new建立對象時若類沒被初始化,則初始化該類。
但現在通過new要建立的是一個數組對象,而非Fu類對象,是以也屬于間接引用,不會初始化Fu類。
- 示例三
public class Fu{
public static final String name = "柴毛毛";
static{
System.out.println("父類被初始化!");
}
}
public class A{
public static void main(String[] args){
System.out.println(Fu.name);
}
}
本示例看似滿足類初始化時機的第一個條件:擷取一個類靜态成員變量的時候若類尚未初始化則初始化類。
但是,Fu類的靜态成員變量被final修飾,它已經是一個常量。被final修飾的常量在Java代碼編譯的過程中就會被放入它被引用的class檔案的常量池中(這裡是A的常量池)。是以程式在運作期間如果需要調用這個常量,直接去目前類的常量池中取,而不需要初始化這個類。
5. 接口的初始化
接口和類都需要初始化,接口和類的初始化過程基本一樣,不同點在于:類初始化時,如果發現父類尚未被初始化,則先要初始化父類,然後再初始化自己;但接口初始化時,并不要求父接口已經全部初始化,隻有程式在運作過程中用到當父接口中的東西時才初始化父接口。