類的生命周期
java類的生命周期就是指一個class文件從加載到卸載的全過程。
完整的生命周期會經(jīng)歷加載、連接、初始化、使用、和卸載五個階段,當(dāng)然也有在加載或者連接之后沒有被初始化就直接被使用的情況,如圖所示:

加載
在java中,我們經(jīng)常會接觸到一個詞——類加載,它和這里的加載并不是一回事,通常我們說類加載指的是類的生命周期中加載、連接、初始化三個階段。在加載階段,找到需要加載的類并把類的信息加載到方法區(qū)中,然后在堆區(qū)中實例化一個java.lang.Class對象,作為方法區(qū)中這個類的信息的入口。對于加載的時機,真正用到一個類的時候才對它進行加載。有時連接階段并不會等加載階段完全完成之后才開始,而是交叉進行,加載階段總是在連接階段之前開始,連接階段總是在加載階段完成之后完成。
連接
連接階段比較復(fù)雜,一般會跟加載階段和初始化階段交叉進行,可以細分為三個步驟:驗證、準備和解析。
驗證
當(dāng)一個類被加載之后,必須要驗證一下這個類是否合法,保證加載的類是能夠被jvm所運行。比如這個類是不是符合字節(jié)碼的
格式、變量與方法是不是有重復(fù)、數(shù)據(jù)類型是不是有效、繼承與實現(xiàn)是否合乎標(biāo)準等等。
準備
準備階段的工作就是為類的靜態(tài)變量分配內(nèi)存并設(shè)為jvm默認的初值,對于非靜態(tài)的變量,則不會為它們分配內(nèi)存。有一點需要注意,這時候,靜態(tài)變量的初值為jvm默認的初值,而不是我們在程序中設(shè)定的初值。jvm默認的初值是這樣的:基本類型(int、long、short、char、byte、boolean、float、double)的默認值為0,引用類型的默認值為null。常量的默認值為我們程序中設(shè)定的值,比如我們在程序中定義final static int a = 100,則準備階段中a的初值就是100。
解析
這一階段的任務(wù)就是把常量池中的符號引用轉(zhuǎn)換為直接引用。那么什么是符號引用,什么又是直接引用呢?比如我們要在內(nèi)存中找一個類里面的一個叫做show的方法,顯然是找不到。但是在解析階段,jvm就會把show這個名字轉(zhuǎn)換為指向方法區(qū)的的一塊內(nèi)存地址, jvm會將所有的類或接口名、字段名、方法名轉(zhuǎn)換為具體的內(nèi)存地址。
初始化
直接引用
會觸發(fā)類的初始化。在java中,直接引用的情況有:
- 通過new關(guān)鍵字實例化對象、讀取或設(shè)置類的靜態(tài)變量、調(diào)用類的靜態(tài)方法。
- 通過反射方式執(zhí)行以上三種行為。
- 初始化子類的時候,會觸發(fā)父類的初始化。
- 作為程序入口直接運行時(也就是直接調(diào)用main方法)。
被動引用
除了以上四種情況,其他使用類的方式叫做被動引用,而被動引用不會觸發(fā)類的初始化。
- 引用父類的靜態(tài)字段,只會引起父類的初始化,而不會引起子類的初始化。
- 定義類數(shù)組,不會引起類的初始化。
- 引用類的常量,不會引起類的初始化。
示例代碼:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class InitClass{
static {
System.out.println("初始化InitClass");
}
public static String a = null;
public static void method(){}
}
class SubInitClass extends InitClass{}
public class Test1 {
/**
* 主動引用引起類的初始化的第四種情況就是運行Test1的main方法時
* 導(dǎo)致Test1初始化,這一點很好理解,就不特別演示了。
* 本代碼演示了前三種情況,以下代碼都會引起InitClass的初始化,
* 但由于初始化只會進行一次,運行時請將注解去掉,依次運行查看結(jié)果。
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception{
// 主動引用引起類的初始化一: new對象、讀取或設(shè)置類的靜態(tài)變量、調(diào)用類的靜態(tài)方法。
// new InitClass();
// InitClass.a = "";
// String a = InitClass.a;
// InitClass.method();
// 主動引用引起類的初始化二:通過反射實例化對象、
// 讀取或設(shè)置類的靜態(tài)變量、調(diào)用類的靜態(tài)方法。
// Class cls = InitClass.class;
// cls.newInstance();
// Field f = cls.getDeclaredField("a");
// f.get(null);
// f.set(null, "s");
// Method md = cls.getDeclaredMethod("method");
// md.invoke(null, null);
// 主動引用引起類的初始化三:實例化子類,引起父類初始化。
// new SubInitClass();
}
}
類的初始化過程
先看一個例子:
public class Insect {
private int i = 9;
protected int j ;
private int x2 = printInt("Inset.x2 init\n");
public Insect() {
System.out.print("i=" + i + ",j = " + j +"\n");
j=39;
}
private int x3 = printInt("Inset.x3 init\n");
static {
System.out.print("父類靜態(tài)代碼塊初始化\n");
}
{
System.out.print("父類代碼塊初始化\n");
}
private static int x1 = printInt("Inset.x1 init\n");
static int printInt(String s) {
System.out.print(s);
return 47;
}
}
public class Beetle extends Insect {
private int k = printInt("Beetle.k init\n");
private int x3 = printInt("Beetle.x3 is init\n");
public Beetle() {
System.out.print("s=" + s + ",");
System.out.print("k=" + k + ",");
System.out.print("j=" + j);
}
static {
System.out.print("子類靜態(tài)代碼塊初始化\n");
}
{
System.out.print("子類代碼塊初始化\n");
}
private int x4 = printInt("Beetle.x4 is init\n");
private static int x2 = printInt("Beetle.x2 is init\n");
private int s = printInt("Beetle.s init\n");
/**
* @param args
*/
public static void main(String[] args) {
System.out.print("Starting\n");
Beetle b = new Beetle();// 情況1
int a = Beetle.x2;// 情況2
}
}
情況1運行結(jié)果:
父類靜態(tài)代碼塊初始化
Inset.x1 init
子類靜態(tài)代碼塊初始化
Beetle.x2 is init
Starting
Inset.x2 init
Inset.x3 init
父類代碼塊初始化
i=9,j = 0
Beetle.k init
Beetle.x3 is init
子類代碼塊初始化
Beetle.x4 is init
Beetle.s init
s=47,k=47,j=39
情況2運行結(jié)果:
父類靜態(tài)代碼塊初始化
Inset.x1 init
子類靜態(tài)代碼塊初始化
Beetle.x2 is init
Starting
結(jié)論:
- 在非實例化對象的初始化,只會初始化與類相關(guān)的靜態(tài)賦值語句和靜態(tài)語句,也就是有static關(guān)鍵字修飾的。
- 沒有static修飾的賦值語句和執(zhí)行語句在實例化對象的時候才會運行。
- 首先會順序初始化父類初始化static 變量 或者 靜態(tài)初始化塊,然后子類
順序初始化父類普通變量 或者 父類普通變量初始化塊 ,然后是構(gòu)造函數(shù)
順序初始化子類普通變量 或者 子類普通變量初始化塊 ,然后是構(gòu)造函數(shù)
使用
類的使用包括主動引用和被動引用
示例代碼:
class InitClass{
static {
System.out.println("初始化InitClass");
}
public static String a = null;
public final static String b = "b";
public static void method(){}
}
class SubInitClass extends InitClass{
static {
System.out.println("初始化SubInitClass");
}
}
public class Test4 {
public static void main(String[] args) throws Exception{
// String a = InitClass.a;// 引用父類的靜態(tài)字段,只會引起父類初始化
// String b = InitClass.b;// 使用類的常量不會引起類的初始化
SubInitClass[] sc = new SubInitClass[10];// 定義類數(shù)組不會引起類的初始化
}
}
卸載
在類使用完之后,如果滿足下面的情況,類就會被卸載:
- 該類所有的實例都已經(jīng)被回收,也就是java堆中不存在該類的任何實例。
- 加載該類的ClassLoader已經(jīng)被回收。
- 該類對應(yīng)的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射訪問該類的方法。
如果以上三個條件全部滿足,jvm就會在方法區(qū)垃圾回收的時候?qū)︻愡M行卸載。
類的卸載過程其實就是在方法區(qū)中清空類信息,java類的整個生命周期就結(jié)束了。
總結(jié)
對于對象的生命周期大家可能都比較熟悉,對象基本上都是在jvm的堆區(qū)中創(chuàng)建,在創(chuàng)建對象之前,會觸發(fā)類加載(加載、連接、初始化),當(dāng)類初始化完成后,根據(jù)類信息在堆區(qū)中實例化類對象,初始化非靜態(tài)變量、非靜態(tài)代碼以及默認構(gòu)造方法,當(dāng)對象使用完之后會在合適的時候被jvm垃圾收集器回收。讀完本文后我們知道,對象的生命周期只是類的生命周期中使用階段的主動引用的一種情況(即實例化類對象)。而類的整個生命周期則要比對象的生命周期長的多。