Java類的加載機制(類加載和初始化順序)

Java類加載機制中最重要的就是程序初始化過程缰雇,其中包含了靜態(tài)資源皆串,非靜態(tài)資源淹办,父類子類,構造方法之間的執(zhí)行順序恶复。這類知識經(jīng)常會出現(xiàn)在面試題中怜森,如果沒有搞清楚其原理,在復雜的開源設計中可能無法梳理其業(yè)務流程谤牡,是java程序員進階的阻礙副硅。

首先通過一個例子來分析java代碼的執(zhí)行順序:

    public class CodeBlockForJava extends BaseCodeBlock {
        {
            System.out.println("這里是子類的普通代碼塊");
        }
        public CodeBlockForJava() {
            System.out.println("這里是子類的構造方法");
        }
        @Override
        public void msg() {
              System.out.println("這里是子類的普通方法");
        }

        public static void msg2() {
            System.out.println("這里是子類的靜態(tài)方法");
        }

        static {
            System.out.println("這里是子類的靜態(tài)代碼塊");
        }

        public static void main(String[] args) {
            BaseCodeBlock bcb = new CodeBlockForJava();
            bcb.msg();
        }
        Other o = new Other();
    }

    class BaseCodeBlock {

        public BaseCodeBlock() {
            System.out.println("這里是父類的構造方法");
        }

        public void msg() {
            System.out.println("這里是父類的普通方法");
        }

        public static void msg2() {
            System.out.println("這里是父類的靜態(tài)方法");
        }

        static {
            System.out.println("這里是父類的靜態(tài)代碼塊");
        }

        Other2 o2 = new Other2();

        {
            System.out.println("這里是父類的普通代碼塊");
        }
    }

    class Other {
        Other() {
            System.out.println("初始化子類的屬性值");
        }
    }

    class Other2 {
        Other2() {
            System.out.println("初始化父類的屬性值");
        }
    }

這個例子比較簡單,在運行代碼之前分析一下:帶有static關鍵字的代碼塊應該是最先執(zhí)行翅萤,其次是非static關鍵字的代碼塊以及類的屬性(Fields),最后是構造方法恐疲。帶上父子類的關系后,上面的運行結果為:

    這里是父類的靜態(tài)代碼塊
    這里是子類的靜態(tài)代碼塊
    初始化父類的屬性值
    這里是父類的普通代碼塊
    這里是父類的構造方法
    這里是子類的普通代碼塊
    初始化子類的屬性值
    這里是子類的構造方法
    這里是子類的普通方法

注意的是類的屬性與非靜態(tài)代碼塊的執(zhí)行級別是一樣的,誰先執(zhí)行取決于書寫的先后順序培己。
結論1:父類的靜態(tài)代碼塊->子類的靜態(tài)代碼塊->初始化父類的屬性值/父類的普通代碼塊(自上而下的順序排列)->父類的構造方法->初始化子類的屬性值/子類的普通代碼塊(自上而下的順序排列)->子類的構造方法糜烹。
注:構造函數(shù)最后執(zhí)行。

上面的例子只是小試牛刀漱凝,接下來再看一個比較復雜的例子:

    public class ClassloadSort1 {

        public static void main(String[] args) {
            Singleton.getInstance();
            System.out.println("Singleton value1:" + Singleton.value1);
            System.out.println("Singleton value2:" + Singleton.value2);
    
            Singleton2.getInstance2();
            System.out.println("Singleton2 value1:" + Singleton2.value1);
            System.out.println("Singleton2 value2:" + Singleton2.value2);
        }
    }
    
    class Singleton {
        static {
            System.out.println(Singleton.value1 + "\t" + Singleton.value2 + "\t" + Singleton.singleton);
            //System.out.println(Singleton.value1 + "\t" + Singleton.value2);
        }
        private static Singleton singleton = new Singleton();
        public static int value1 = 5;
        public static int value2 = 3;
    
        private Singleton() {
            value1++;
            value2++;
        }

        public static Singleton getInstance() {
            return singleton;
        }

        int count = 10;

        {
            System.out.println("count = " + count);
        }
    }
    
    class Singleton2 {
        static {
            System.out.println(Singleton2.value1 + "\t" + Singleton2.value2 + "\t" + Singleton2.singleton2);
        }

        public static int value1 = 5;
        public static int value2 = 3;
        private static Singleton2 singleton2 = new Singleton2();
        private String sign;

        int count = 20;
        {
            System.out.println("count = " + count);
        }

        private Singleton2() {
            value1++;
            value2++;
        }

        public static Singleton2 getInstance2() {
            return singleton2;
        }
    }

這個用例相比第一個疮蹦,知識點更深了一層。如果你用結論1是沒法分析出正確答案的茸炒,但這并不代表結論1就是錯誤的愕乎。
運行結果:

    Singleton value1:5
    Singleton value2:3

    Singleton2 value1:6
    Singleton2 value2:4

Singleton中的value1,value2并沒有受到構造方法中自加操作的影響。然而Singleton2中的代碼也相同壁公,為什么執(zhí)行出來的效果就不一樣呢感论?
要想知道原因,必須先搞清楚Java類加載中具體做了些什么紊册。

JAVA類的加載機制
Java類加載分為5個過程,分別為:加載比肄,連接(驗證,準備囊陡,解析)芳绩,初始化,使用撞反,卸載妥色。

  1. 加載
    加載主要是將.class文件(也可以是zip包)通過二進制字節(jié)流讀入到JVM中。 在加載階段遏片,JVM需要完成3件事:
    1)通過classloader在classpath中獲取XXX.class文件嘹害,將其以二進制流的形式讀入內存。
    2)將字節(jié)流所代表的靜態(tài)存儲結構轉化為方法區(qū)的運行時數(shù)據(jù)結構吮便;
    3)在內存中生成一個該類的java.lang.Class對象笔呀,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。

2.1. 驗證
主要確保加載進來的字節(jié)流符合JVM規(guī)范髓需。驗證階段會完成以下4個階段的檢驗動作:
1)文件格式驗證
2)元數(shù)據(jù)驗證(是否符合Java語言規(guī)范)
3)字節(jié)碼驗證(確定程序語義合法许师,符合邏輯)
4)符號引用驗證(確保下一步的解析能正常執(zhí)行)
2.2. 準備
準備是連接階段的第二步,主要為靜態(tài)變量在方法區(qū)分配內存授账,并設置默認初始值枯跑。
2.3. 解析
解析是連接階段的第三步惨驶,是虛擬機將常量池內的符號引用替換為直接引用的過程白热。

  1. 初始化
    初始化階段是類加載過程的最后一步,主要是根據(jù)程序中的賦值語句主動為類變量賦值粗卜。
    當有繼承關系時屋确,先初始化父類再初始化子類,所以創(chuàng)建一個子類時其實內存中存在兩個對象實例。
    注:如果類的繼承關系過長攻臀,單從類初始化角度考慮焕数,這種設計不太可取。原因我想你已經(jīng)猜到了刨啸。
    通常建議的類繼承關系最多不超過三層堡赔,即父-子-孫。某些特殊的應用場景中可能會加到4層设联,但就此打住善已,第4層已經(jīng)有代碼設計上的弊端了。

  2. 使用
    程序之間的相互調用离例。

  3. 卸載
    即銷毀一個對象换团,一般情況下中有JVM垃圾回收器完成。代碼層面的銷毀只是將引用置為null宫蛆。

通過上面的整體介紹后艘包,再來看Singleton2.getInstance()的執(zhí)行分析:
1)類的加載。運行Singleton2.getInstance(),JVM在首次并沒有發(fā)現(xiàn)Singleton類的相關信息耀盗。所以通過classloader將Singleton.class文件加載到內存中想虎。
2)類的驗證。略
3)類的準備叛拷。將Singleton2中的靜態(tài)資源轉化到方法區(qū)磷醋。value1,value2胡诗,singleton在方法區(qū)被聲明分別初始為0邓线,0,null煌恢。
4)類的解析骇陈。略(將常量池內的符號引用替換為直接引用的過程)
5)類的初始化。執(zhí)行靜態(tài)屬性的賦值操作瑰抵。按照順序先是value1 = 5你雌,value2 = 3,接下來是private static Singleton2 singleton2 = new Singleton2();
這是個創(chuàng)建對象操作,根據(jù) 結論1 在執(zhí)行Singleton2的構造方法之前二汛,先去執(zhí)行static資源和非static資源婿崭。但由于value1,value2已經(jīng)被初始化過,所以接下來執(zhí)行的是非static的資源肴颊,最后是Singleton2的構造方法:value1++;value2++氓栈。
所以Singleton2結果是6和4。

以上除了搞清楚執(zhí)行順序外婿着,還有一個重點->結論2:靜態(tài)資源在類的初始化中只會執(zhí)行一次授瘦。不要與第3個步驟混淆醋界。

有了以上的這個結論,再來看Singleton.getInstance()的執(zhí)行分析:
1)類的加載提完。將Singleton類加載到內存中形纺。
2)類的驗證。略
3)類的準備徒欣。將Singleton2的靜態(tài)資源轉化到方法區(qū)逐样。
4)類的解析。略(將常量池內的符號引用替換為直接引用的過程)
5)類的初始化打肝。執(zhí)行靜態(tài)屬性的賦值操作官研。按照順序先是private static Singleton singleton = new Singleton(),根據(jù) 結論1結論2闯睹,value1和value2不會在此層執(zhí)行賦值操作戏羽。所以singleton對象中的value1,value2只是在0的基礎上進行了++操作。此時singleton對象中的value1=1,value2=1楼吃。
然后始花, public static int value1 = 5; public static int value2 = 3; 這兩行代碼才是真的執(zhí)行了賦值操作。所以最后的結果:5和3孩锡。
如果執(zhí)行的是public static int value1; public static int value2;結果又會是多少酷宵?結果: 1和1。

注:為什么 Singleton singleton = new Singleton()不會對value1,value2進行賦值操作躬窜?因為static變量的賦值在類的初始化中只會做一次浇垦。
程序在執(zhí)行private static Singleton singleton = new Singleton()時,已經(jīng)是對Singleton類的static變量進行賦值操作了荣挨。這里new Singleton()是一個特殊的賦值男韧,類似于遞歸里層,外層已經(jīng)是賦值操作了默垄,所以里層會自動過濾static變量的賦值操作此虑。但非static的變量依然會被賦值。

結論3:在結論2的基礎上口锭,非靜態(tài)資源會隨對象的創(chuàng)建而執(zhí)行初始化朦前。每創(chuàng)建一個對象,執(zhí)行一次初始化鹃操。

掌握結論1韭寸,2,3基本對java類中程序執(zhí)行的順序了如指掌荆隘。這還不夠恩伺,ClassLoader還沒有介紹呢。

類加載器

JVM提供了以下3種系統(tǒng)的類加載器:

  • 啟動類加載器(Bootstrap ClassLoader):最頂層的類加載器臭胜,負責加載 JAVA_HOME\lib 目錄中的莫其,或通過-Xbootclasspath參數(shù)指定路徑中的癞尚,且被虛擬機認可(按文件名識別耸三,如rt.jar)的類乱陡。
  • 擴展類加載器(Extension ClassLoader):負責加載 JAVA_HOME\lib\ext 目錄中的,或通過java.ext.dirs系統(tǒng)變量指定路徑中的類庫仪壮。
  • 應用程序類加載器(Application ClassLoader):也叫做系統(tǒng)類加載器憨颠,可以通過getSystemClassLoader()獲取,負責加載用戶路徑(classpath)上的類庫积锅。如果沒有自定義類加載器爽彤,一般這個就是默認的類加載器。

這3種類加載器先了解一下是什么即可缚陷,由于篇幅和主題限制适篙,類加載器將會在下篇文件詳細介紹。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末箫爷,一起剝皮案震驚了整個濱河市嚷节,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌虎锚,老刑警劉巖硫痰,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異窜护,居然都是意外死亡效斑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門柱徙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缓屠,“玉大人,你說我怎么就攤上這事护侮〔匮校” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵概行,是天一觀的道長蠢挡。 經(jīng)常有香客問我,道長凳忙,這世上最難降的妖魔是什么业踏? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮涧卵,結果婚禮上勤家,老公的妹妹穿的比我還像新娘。我一直安慰自己柳恐,他們只是感情好伐脖,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布热幔。 她就那樣靜靜地躺著,像睡著了一般讼庇。 火紅的嫁衣襯著肌膚如雪绎巨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天蠕啄,我揣著相機與錄音场勤,去河邊找鬼。 笑死歼跟,一個胖子當著我的面吹牛和媳,可吹牛的內容都是我干的。 我是一名探鬼主播哈街,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼留瞳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了骚秦?” 一聲冷哼從身側響起她倘,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎骤竹,沒想到半個月后帝牡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡蒙揣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年靶溜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片懒震。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡罩息,死狀恐怖,靈堂內的尸體忽然破棺而出个扰,到底是詐尸還是另有隱情瓷炮,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布递宅,位于F島的核電站娘香,受9級特大地震影響,放射性物質發(fā)生泄漏办龄。R本人自食惡果不足惜烘绽,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望俐填。 院中可真熱鬧安接,春花似錦、人聲如沸英融。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至胡野,卻和暖如春材失,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背给涕。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工豺憔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留额获,地道東北人够庙。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像抄邀,于是被迫代替她去往敵國和親耘眨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容