Java類的生命週期和加載機制

#1、類的生命週期 類從被加載到虛擬機內存開始,到卸載出內存爲止,整個生命週期包括:加載、驗證、準備、解析、初始化、使用和卸載七個階段。 其中加載、驗證、準備、初始化、和卸載這5個階段的順序是肯定的。而解析階段不必定:它在某些狀況下能夠在初始化階段以後再開始,這是爲了支持Java的運行時綁定。
關於初始化:JVM規範明確規定,有且只有5中狀況必須執行對類的初始化(加載、驗證、準備天然再此以前要發生):html

  1. 遇到new、getstatic、putstatic、invokestatic,若是類沒有初始化,則必須初始化,這幾條指令分別是指:new新對象、讀取靜態變量、設置靜態變量,調用靜態函數。
  2. 使用java.lang.reflect包的方法對類進行反射調用時,若是類沒初始化,則須要初始化
  3. 當初始化一個類時,若是發現父類沒有初始化,則須要先觸發父類初始化。
  4. 當虛擬機啓動時,用戶須要制定一個執行的主類(包含main函數的類),虛擬機會先初始化這個類。
  5. 可是用JDK1.7啓的動態語言支持時,若是一個MethodHandle實例最後解析的結果是REF_getStatic、REF_putStatic、Ref_invokeStatic的方法句柄時,而且這個方法句柄所對應的類沒有進行初始化,則要先觸發其初始化。

注意:經過子類來引用父類的靜態字段,不會致使子類初始化java

public class SuperClass{
    public static int value = 123;
    static{
        System.out.printLn("SuperClass init!");
    }
}

public class SubClass extends SuperClass{
    static{
        System.out.println("SubClass init!");
    }
}

public class Test{
    public static void main(String[] args){
        System.out.println(SubClass.value);
    }
}

最後只會打印:SuperClass init!
常量會在編譯階段存入調用者的常量池,本質上並無直接引用到定義常量的類,所以不會觸發定義常量的類初始化程序員

public class ConstClass{
    public static final String HELLO_WORLD = "hello world";
    static {
        System.out.println("ConstClass init!");
    }
}

public class Test{
    public static void main(String[] args){
        System.out.print(ConstClass.HELLO_WORLD);
    }
}

最後結果不會出現「ConstClass init!」,只會打印:hello world!
加載安全

加載過程主要作如下3件事數據結構

  1. 經過一個類的全限定名稱來獲取此類的二進制流
  2. 將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構
  3. 在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據訪問入口。

驗證函數

這個階段主要是爲了確保Class文件字節流中包含信息符合當前虛擬機的要求,而且不會出現危害虛擬機自身的安全。.net

準備code

準備階段是正式爲類變量分配內存並設置類變量初始值的階段,這些變量所使用的內存都在方法區中分配。首先,這個時候分配內存僅僅包括類變量(被static修飾的變量),而不包括實例變量。實例變量會在對象實例化時隨着對象一塊兒分配在java堆中。其次這裏所說的初始值「一般狀況下」是數據類型的零值,假設一個類變量定義爲htm

public static int value = 123;

那變量value在準備階段後的初始值是0,而不是123,由於尚未執行任何Java方法,而把value賦值爲123是在程序編譯後,存放在類構造函數< clinit >()方法中。
解析對象

解析階段是把虛擬機中常量池的符號引用替換爲直接引用的過程。

初始化

類初始化時類加載的最後一步,前面類加載過程當中,除了加載階段用戶能夠經過自定義類加載器參與之外,其他動做都是虛擬機主導和控制。到了初始化階段,纔是真正執行類中定義Java程序代碼。

準備階段中,變量已經賦過一次系統要求的初始值,而在初始化階段,根據程序員經過程序制定的主觀計劃初始化類變量。初始化過程實際上是執行類構造器< clinit >()方法的過程。

< clinit >()方法是由編譯器自動收集類中全部類變量的賦值動做和靜態語句塊中的語句合併產生的。收集的順序是按照語句在源文件中出現的順序。靜態語句塊中只能訪問定義在靜態語句塊以前的變量,定義在它以後的變量能夠賦值,但不能訪問。以下所示:

public class Test {
    static{
        i = 0;//給變量賦值,能夠經過編譯
        System.out.print(i);//這句編譯器會提示:「非法向前引用」
    }
    static int i = 1;
}

< clinit >()方法與類構造函數(或者說實例構造器< init >())不一樣,他不須要顯式地調用父類構造器,虛擬機會保證子類的< clinit >()方法執行以前,父類的< clinit >()已經執行完畢。

卸載
當表明一個類的Class對象再也不被引用,即不可觸及時,Class對象就會結束生命週期,該類在方法區內的數據也會被卸載,從而結束類的生命週期。
由Java虛擬機自帶的類加載器所加載的類,在虛擬機的生命週期中,始終不會被卸載。Java虛擬機自帶的類加載器包括根類加載器、擴展類加載器和系統類加載器。

#2、加載機制
Java類的初始化

本階段負責爲類變量賦正確的初始值。(類變量即靜態變量)
Java編譯器把全部的類變量初始化語句和靜態初始化器統統收集到<clinit>方法中,該方法只能被JVM調用,專門承擔初始化工做。 初始化一個類必須保證其直接超類已被初始化。
並不是全部類都擁有<clinit>()方法,如下類不會擁有<clinit>方法:

  1. 該類既沒有聲明任何類變量,也沒有靜態初始化語句。
  2. 該類聲明瞭類變量,但沒有使用類變量初始化語句或靜態初始化語句初始化。
  3. 該類只包含靜態final變量的類變量初始化語句,而且類變量初始化語句是常量表達式。

Java類初始化的時機
規範定義類的初始化時機爲「initialize on first active use」,即「在首次主動使用時初始化」。裝載和連接在初始化以前就要完成。
首次主動使用的情形:

  1. 建立類的新實例--new,反射,克隆或反序列化;
  2. 調用類的靜態方法;
  3. 操做類和接口的靜態字段;(final字段除外)
  4. 調用Java的特定的反射方法;
  5. 初始化一個類的子類;
  6. 指定一個類做爲Java虛擬機啓動時的初始化類(含有main方法的啓動類)。

除了以上6種情形,java中類的其餘使用方式都是被動使用,不會致使類的初始化。

Java對象初始化
編譯器爲每一個類生成至少一個實例初始化方法,即<init>()方法。此方法與源程序裏的每一個構造方法對應。若是類沒有聲明構造方法,則生成一個默認構造方法,該方法僅調用父類的默認構造方法,同時生成與該默認構造方法對應的<init>()方法。 <init>()方法內容大概爲:

  1. 調用另外一個<init>()方法(本類的另一個<init>()方法或父類的<init>()方法);
  2. 初始化實例變量;
  3. 與其對應的構造方法內的字節碼

Java對象初始化的時機
對象初始化又稱爲對象實例化。Java對象在其被建立時初始化。
有兩種方式建立Java對象:一種是顯示對象建立,經過new關鍵字來調用一個類的構造函數,經過構造函數建立一個對象。

#Thanks http://www.cnblogs.com/bigbigheart/p/6009565.html
http://blog.csdn.net/moreevan/article/details/6968718
http://www.cnblogs.com/jxzheng/p/5191037.html

相關文章
相關標籤/搜索