簡述JAVA類的生命周期


介紹

一個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類的構造函數執行[非靜態屬性]!
子類的構造代碼塊!
子類構造函數!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM