介紹
一個java類的完整的生命周期會經歷加載、連接、初始化、使用、和卸載五個階段:
加載
主要是:把類的信息加載到方法區中,並在堆中實例化一個Class對象。
加載方式
根據類的全路徑加載class文件
從jar的包中讀取class文件
根據一定的規則實時生成,比如設計模式中的動態代理模式,就是根據相應的類自動生成它的代理類。
加載的時期
不是jvm啟動就加載,而是在真是使用的時候才會觸發加載。
- new 一個類的時候
- 調用類的靜態方法,以及讀取或者修改一個類的靜態字段的時候(不是常量)
- 這個類是程序的入口類
- 對這個類進行反射的時候(執行了上面的行為)
連接
一般會跟加載階段和初始化階段交叉進行。
驗證
驗證一下這個類是否合法,
- 字節碼格式是否合法
- 變量和方法是否有重復
- 繼承和實現是否符合標准
。。。
准備
給類的靜態變量分配並初始化存儲空間;
也就是給靜態變量賦默認的初始值(不包括非靜態變量)
解析
把符合引用轉換為直接引用。
比如我們要在內存中找一個類里面的一個叫做show的方法,顯然是找不到。但是在解析階段,
jvm就會把show這個名字轉換為指向方法區的的一塊內存地址,比如c17164,通過c17164就可以找到show這個方法具體分配在內存的哪一個區域了。
這里show就是符號引用,而c17164就是直接引用。
在解析階段jvm會將所有的類或接口名、字段名、方法名轉換為具體的內存地址。
初始化
執行靜態變量的初始化和靜態Java代碼塊,並初始化程序員設置的變量值!
時機
和加載的時機一樣,更准確的說初始化之前必須先經過加載,所以他們基本一樣
- new 一個類的時候
- 調用類的靜態方法,以及讀取或者修改一個類的靜態字段的時候(不是常量)
- 對這個類進行反射的時候(執行了上面的行為)
- 初始化一個類的子類,該子類所有的父類都會被初始化。
- 作為程序的入口類(如:main方法所在的類,java 命令跟着的類)
過程
按照順序自上而下運行類中的【變量賦值語句】和【靜態語句】,
如果有父類,則首先按照順序運行父類中的變量賦值語句和靜態語句。
使用
使用階段包括主動引用和被動引用。
主動引用(會引起類的初始化)
- new 一個類的時候
- 調用類的靜態方法,以及讀取或者修改一個類的靜態字段的時候(不是常量)
- 這個類是程序的入口類
- 對這個類進行反射的時候(執行了上面的行為)
注意:以上幾種主動引用會引起類的初始化,這里的幾種情況只有new或者使用class.newInstance()才會調用構造函數!
被動引用(不會引起類的初始化)
引用父類的靜態字段
定義類數組
引用類的常量
例如
public class TestClassInit {
static {
System.out.println("初始化InitClass");
}
public static String staticA = null;
public final static String finalB = "b";
public static void staticMethod(){}
}
public class Test {
public static void main(String args []){
//主動引用
//new 一個類的時候
TestClassInit tct=new TestClassInit();
//讀取靜態變量
String staticA=TestClassInit.staticA;
//設置靜態變量
TestClassInit.staticA="A";
//調用靜態方法
TestClassInit.staticMethod();
//被動引用
//定義類數組,不會引起類的初始化
TestClassInit[] tcts=new TestClassInit[10];
//引用類的常量,不會引起類的初始化
String finalB=TestClassInit.finalB;
}
}
卸載
滿足下面請看才會卸載類:
- java堆中不存在該類的任何實例。
- 加載該類的ClassLoader已經被回收
- 該類對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射訪問該類的方法
相關問題
Class.forName()和ClassLoader.loadClass的區別
Class.forName(className)方法,內部實際調用的方法是 Class.forName(className,true,classloader);
第2個boolean參數表示類是否需要初始化, Class.forName(className)默認是需要初始化。
一旦初始化,就會觸發目標對象的 static塊代碼執行,static參數也也會被再次初始化。
ClassLoader.loadClass(className)方法,內部實際調用的方法是 ClassLoader.loadClass(className,false);
第2個 boolean參數,表示目標對象是否進行鏈接,false表示不進行鏈接,由上面介紹可以,
不進行鏈接意味着不進行包括初始化等一些列步驟,那么靜態塊和靜態對象就不會得到執行;
如:Student.class.getClassLoader().loadClass("com.we.web.erp.jvm.Student");
Class.forName("com.we.web.erp.jvm.Student");
靜態代碼塊,構造代碼塊,構造函數,以及靜態變量賦值, 實例變量賦值 的執行順序;
- 沒有繼承關系的情況下:
靜態變量賦值 > 靜態代碼 > 實例變量賦值 > 構造代碼 > 構造函數
public class School {
public School(){
System.out.println("School的構造函數執行[靜態屬性]!");
}
}
public class Student {
public Student(){
System.out.println("Student類的構造函數執行[非靜態屬性]!");
}
}
public class SubClass {
private Student student=new Student();
private static School school=new School();
static
{
System.out.println("靜態代碼塊!");
}
{
System.out.println("構造代碼塊!");
}
public SubClass()
{
System.out.println("構造函數!");
}
}
執行結果:
School的構造函數執行[靜態屬性]!
靜態代碼塊!
Student類的構造函數執行[非靜態屬性]!
構造代碼塊!
構造函數!
執行這里的靜態變量賦值和實例變量賦值可以按照下圖理解;
- 有繼承的情況下
public class SubClass extends SuperClass {
private Student student=new Student();
private static School school=new School();
static
{
System.out.println("子類靜態代碼塊!");
}
{
System.out.println("子類的構造代碼塊!");
}
public SubClass()
{
System.out.println("子類構造函數!");
}
}
public class SuperClass {
static {
System.out.println("父類靜態代碼塊!");
}
{
System.out.println("父類的構造代碼塊!");
}
SuperClass()
{
System.out.println("父類的構造函數!");
}
}
執行結果:
父類靜態代碼塊!
School的構造函數執行[靜態屬性]!
子類靜態代碼塊!
父類的構造代碼塊!
父類的構造函數!
Student類的構造函數執行[非靜態屬性]!
子類的構造代碼塊!
子類構造函數!