Class的生命週期

         之前的JVM類加載機制-ClassLoader初探JVM-ClassLoader源碼,只是討論了Class的加載部分,現在來縱觀一下整個Class的生命週期。

         Class的生命週期就是指一個class文件(字節碼)從加載到卸載的全過程。

當一個類被裝載、連接、初始化後,它的生命週期就開始了,當代表該類的Class對象不再被引用、即已經不可觸及的時候,Class對象的生命週期結束。那麼該類的方法區內的數據也會被卸載,從而結束該類的生命週期。

一個類的生命週期取決於它Class對象的生命週期,經歷加載、連接、初始化、使用、和卸載五個階段。

         類的加載(Load Class)包含了以下三個步驟:

裝載 Loading

查找Class的二進制文件(.class),把類的信息加載到JVM的方法區中,對其進行部分檢驗(類文件的魔數,文件長度,是否有父類等);在堆區中實例化一個java.lang.Class對象,作爲方法區中這個類的信息的入口。

加載方式有多種:A. 在classPath下找相應class文件;B. 從jar文件中讀取;C. 從網絡中獲取;D. 實時生成,如設計模式中的動態代理模式;E. 從非class文件中獲取,這些文件在jvm中運行之前也會被轉換爲可識別的字節碼文件。

         一般來說加載和連接是同步的,但有時候也會交叉進行,但是兩者的開始和結束是順序的。

 

鏈接 Linking

         將對應的字節碼文件讀入到JVM中(其中解析步驟是可以選擇的)

a)        驗證

         檢查載入的class文件數據的合法性:字節碼的格式,變量與方法是否重複、數據類型是否有效、繼承與實現是否合法,接入屬性是否正確(public ,private的問題),檢查 final class 有沒有被繼承,檢查靜態變量的正確性等等。

b)        準備

         給類的靜態變量分配存儲空間:賦default值(基本類型爲0,引用爲null),靜態常量(如final static int a = 100),但不會對其進行初始化,不會執行任何 Java 代碼(static塊)。

c)        解析

         將符號引用轉成直接引用,完成內存結構的佈局。

         例如我們要調用Collections.toString(),java.util.Collections.toString()就是符號引用,而直接引用就是這個方法在方法區中的內存地址。解析就是把類、接口、方法、成員變量的符號引用轉換成內存地址,以供調用。

         在連接階段完成後,再根據使用的情況(直接引用or被動引用)來決定是否類進行初始化。

 

初始化 Initializing

         對靜態變量賦assign值,執行靜態代碼塊。   【類的初始化順序可以參考我的《類的初始化&實例化順序》】

         在Java中類的引用分爲直接引用和被動引用,只有直接引用,會觸發類的初始化:

         a) new實例化對象;b) 使用類的(非常量)靜態變量 / 方法;c) 通過反射執行前三種情況;d) 子類被初始化;e) 作爲程序入口,調用main(也是調用靜態方法的一種)。

         其他使用類的方式都叫被動引用,如:

         a) 定義類數組;b) 引用類的靜態常量;c) 引用父類的靜態域,只會觸發父類的初始化,而不會觸發子類的初始化。

         從上面能看出,初始化的原則是,只初始化要用到的。在主動引用中,實例化(new/main)、使用靜態域都是被用到了;而子類依賴了父類,所以也會觸發初始化。在被動引用中,a中的類型只是用於編譯器的校驗,而b中的靜態常量是在鏈接中的準備階段已經完成了,所以兩者都用不上去初始化;另外c中只用到了父類的靜態域,所以就沒必要觸發子類。

public class LoaderLazy {
    publicstatic String HELLO = "Hello";
    static{
       System.out.println("Init parent");
    }
class SubLoaderLazy extends LoaderLazy{
    static{
       System.out.println("Init sub");
    }
}
    System.out.println(SubLoaderLazy.HELLO);

console:

Init parent

         能看出上面只初始化了父類。那如果我們把HELLO屬性放在子類SubLoaderLazy中,子類也會被初始化。

console:

Init parent

Init sub

 

實例化 NewInstance

         類的初始化完成後,我們就可以創建對象實例了。

         相比初始化只執行類的靜態域(靜態變量/代碼塊),類的實例化是執行類的實例域(非靜態變量/代碼塊)和構造函數,並且在堆區創建一個類的實例對象。

(在實例化時,如果沒有指定構造函數,JVM會自動構造一個無參構造函數。)

         類的實例化,一般在使用new創建對象,或者Class.newInstance()時執行。

【類的實例化順序可以參考我的《類的初始化&實例化順序》】

 

回收卸載 GC

         Class作爲JVM中的一個特殊對象,也會被GC回收卸載。

         Class的卸載就是清空方法區中Class的信息和堆區中的java.lang.Class對象。這時Class的聲明週期就結束了。

         SUN的原話:“class or interface may be unloaded if andonly if its class loader is unreachable. Classesloaded by the BootstrapClassLoadermay not be unloaded”。

 

         Class被回收要滿足以下三個條件:

a)        No Instance:該類所有的實例都已經被GC;

b)        No ClassLoader:加載該類的ClassLoader實例已經被GC;

c)        No Reference:該類的java.lang.Class對象沒有被引用。(XXX.class, 靜態變量/方法)

 

         另外,有些Class是不會被回收的:

1、根據JVM和JLS的規範,由Bootstrap類加載器加載的Class在整個運行期間是不會被卸載的。

2、被Extension類加載器 和System類加載器加載的Class在運行期間不太可能被卸載,因爲這些實例基本上在整個運行期間總能直接或者間接的訪問的到,其達到unreachable的可能性極小。

3、開發者自定義的類加載器加載的Class只有在很簡單的上下文環境中才能被卸載,稍微複雜點的應用場景中,很難有符合上面3個條件的Class。

綜合以上三點,一個已經加載的Class被卸載的機率很小;而且卸載時間也是不可控的。而且Class佔用的內存空間不大,一般不用考慮。

 

參考博客 http://blog.csdn.net/biaobiaoqi/article/details/6909141

發佈了44 篇原創文章 · 獲贊 11 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章