從幾行代碼學習Java類加載機制-從懵逼到放棄

我們先看兩段代碼的運行結果

public class Test1 {
    public static void main(String[] args) {
        System.out.println(FinalTest.NUM);
        System.out.println(FinalTest1.NUM);
    }
}


class FinalTest{
    public static final int NUM = 3;
    static{
        System.out.println("hello");
    }
}

class FinalTest1{
    public static int NUM = 5;
    static{
        System.out.println("hello1");
    }
}

打印結果:

3
hello1
5

問題1:為什么"hello" 沒有被打印出來?

public class Singleton {
    public static Singleton instance = new Singleton();
    public static int a1;
    public static int a2 = 0;

    public Singleton() {
        a1++;
        a2++;
    }

    public static Singleton getSingleton() {
        return instance;
    }
}

public class TestSingleton {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getSingleton();
        System.out.print(singleton.a1);
        System.out.print(singleton.a2);
    }
}

打印結果: 10

問題2:為什么結果不是 11 而是 10? a2為什么是0?

問題1涉及到Java的類加載條件等,問題2涉及到Java類加載步驟,上述兩個問題的答案看完下文便知.

什么是Java的類加載

我們都知道,Java類編譯完會成為.class文件.
類的加載指的是將類的.class文件中的二進制數(shù)據(jù)讀入內(nèi)存中,將其放在運行時數(shù)據(jù)區(qū)域的方法去內(nèi),然后在堆中創(chuàng)建java.lang.Class對象,用來封裝類在方法區(qū)的數(shù)據(jù)結構.只有java虛擬機才會創(chuàng)建class對象,并且是一一對應關系.這樣才能通過反射找到相應的類信息.

誰來加載

Java字節(jié)碼的加載是由類加載器(ClassLoader)加載的.

至于ClassLoader的雙親委托模式,其實就是一個遞歸. 其流程如下

當類加載器收到加載類或資源的請求時龙亲,通常都是先委托給父類加載器加載犹菱,也就是說只有當父類加載器找不到指定類或資源時唉侄,自身才會執(zhí)行實際的類加載過程着绷,具體的加載過程如下:

源 ClassLoader 先判斷該 Class 是否已加載图呢,如果已加載褐桌,則直接返回 Class照雁,如果沒有則委托給父類加載器秋泄。

父類加載器判斷是否加載過該 Class阱持,如果已加載夭拌,則直接返回 Class,如果沒有則委托給祖父類加載器衷咽。

依此類推叁丧,直到始祖類加載器(引用類加載器)疹娶。

始祖類加載器判斷是否加載過該 Class,如果已加載,則直接返回 Class蚕冬,如果沒有則嘗試從其對應的類路徑下尋找 class 字節(jié)碼文件并載入骄崩。如果載入成功映企,則直接返回 Class墨榄,如果載入失敗,則委托給始祖類加載器的子類加載器此蜈。

始祖類加載器的子類加載器嘗試從其對應的類路徑下尋找 class 字節(jié)碼文件并載入即横。如果載入成功,則直接返回 Class裆赵,如果載入失敗东囚,則委托給始祖類加載器的孫類加載器。
依此類推战授,直到源 ClassLoader页藻。

源 ClassLoader 嘗試從其對應的類路徑下尋找 class 字節(jié)碼文件并載入。如果載入成功植兰,則直接返回 Class份帐,如果載入失敗,源 ClassLoader 不會再委托其子類加載器楣导,而是拋出異常废境。

了解更多,可以看關于ClassLoader那點事

類加載條件 (問題1答案)

那么我們什么時候類需要加載呢?

當我們首次主動使用這個類的時候,類需要被加載.

那什么是“主動使用”呢?

  1. 創(chuàng)建對象的實例:我們new對象的時候筒繁,會引發(fā)類的初始化噩凹,前提是這個類沒有被初始化。
  2. 調(diào)用類的靜態(tài)屬性或者為靜態(tài)屬性賦值
  3. 調(diào)用類的靜態(tài)方法
  4. 通過class文件反射創(chuàng)建對象
  5. 初始化一個類的子類:使用子類的時候先初始化父類
  6. java虛擬機啟動時被標記為啟動類的類:就是我們的main方法所在的類
    只有上面6種情況才是主動使用毡咏,也只有上面六種情況的發(fā)生才會引發(fā)類的初始化。

同時我們需要注意下面幾個Tips:

  1. 在同一個類加載器下面只能初始化類一次,如果已經(jīng)初始化了就不必要初始化了.
    這里多說一點血当,為什么只初始化一次呢?因為我們上面講到過類加載的最終結果就是在堆中存有唯一一個Class對象臊旭,我們通過Class對象找到
    類的相關信息落恼。唯一一個Class對象說明了類只需要初始化一次即可离熏,如果再次初始化就會出現(xiàn)多個Class對象,這樣和唯一相違背了滋戳。
  2. 在編譯的時候能確定下來的靜態(tài)變量(編譯常量),不會對類進行初始化;
  3. 在編譯時無法確定下來的靜態(tài)變量(運行時常量),會對類進行初始化;
  4. 如果這個類沒有被加載和連接的話,那就需要進行加載和連接
  5. 如果這個類有父類并且這個父類沒有被初始化,則先初始化父類.
  6. 如果類中存在初始化語句,依次執(zhí)行初始化語句.

這個時候問題1的答案我們可以知道了, 其實FinalTest的靜態(tài)代碼塊沒被執(zhí)行的原因是這個類沒有被加載,因為我們的NUM是final的,屬于編譯常量,在編譯時就確定下來的靜態(tài)變量,不會對類進行初始化.

從字節(jié)碼到內(nèi)存中的對象步驟

從字節(jié)碼到內(nèi)存中的對象, 期間要經(jīng)過三大步, 為 裝載(Loading)、鏈接(Linking)和初始化(Initialization)

加載(Loading)

按如下三步執(zhí)行

  1. 通過類的全名產(chǎn)生對應類的二進制數(shù)據(jù)流奸鸯。(注意咪笑,如果沒找到對應類文件,只有在類實際使用時才拋出錯誤娄涩。)
  2. 分析并將這些二進制數(shù)據(jù)流轉換為方法區(qū)(JVM 的架構:方法區(qū)窗怒、堆,棧蓄拣,本地方法棧扬虚,pc 寄存器)特定的數(shù)據(jù)結構(這些數(shù)據(jù)結構是實現(xiàn)有關的,不同 JVM 有不同實現(xiàn))球恤。這里處理了部分檢驗辜昵,比如類文件的魔數(shù)的驗證,檢查文件是否過長或者過短咽斧,確定是否有父類(除了 Obecjt 類)堪置。
  3. 創(chuàng)建對應類的 java.lang.Class 實例(注意,有了對應的 Class 實例张惹,并不意味著這個類已經(jīng)完成了加載鏈鏈接=)。

鏈接(Linking)

類的連接有三步诵叁,分別是驗證雁竞,準備,解析拧额。

驗證

驗證階段主要做了以下工作

  • 將已經(jīng)讀入到內(nèi)存類的二進制數(shù)據(jù)合并到虛擬機運行時環(huán)境中去碑诉。
  • 類文件結構檢查:格式符合jvm規(guī)范-語義檢查:符合java語言規(guī)范,final類沒有子類,final類型方法沒有被覆蓋
  • 字節(jié)碼驗證:確保字節(jié)碼可以安全的被java虛擬機執(zhí)行.
    二進制兼容性檢查:確保互相引用的類的一致性.如A類的a方法會調(diào)用B類的b方法.那么java虛擬機在驗證A類的時候會檢查B類的b方法是否存在并檢查版本兼容性.因為有可能A類是由jdk1.7編譯的侥锦,而B類是由1.8編譯的进栽。那根據(jù)向下兼容的性質,A類引用B類可能會出錯恭垦,注意是可能快毛。
準備階段

java虛擬機為類的靜態(tài)變量分配內(nèi)存并賦予默認的初始值.如int分配4個字節(jié)并賦值為0,long分配8字節(jié)并賦值為0;

解析階段

解析階段主要是將符號引用轉化為直接引用的過程格嗅。比如 A類中的a方法引用了B類中的b方法,那么它會找到B類的b方法的內(nèi)存地址唠帝,將符號引用替換為直接引用(內(nèi)存地址)屯掖。

初始化步驟(問題2答案)

一種是類有父類,一種是類沒有父類襟衰。(當然所有類的頂級父類都是Object)

  • 沒有父類的情況:

    類的靜態(tài)屬性

    類的靜態(tài)代碼塊

    類的非靜態(tài)屬性

    類的非靜態(tài)代碼塊

    構造方法

  • 有父類的情況:

    父類的靜態(tài)屬性

    父類的靜態(tài)代碼塊

    子類的靜態(tài)屬性

    子類的靜態(tài)代碼塊

    父類的非靜態(tài)屬性

    父類的非靜態(tài)代碼塊

    父類構造方法

    子類非靜態(tài)屬性

    子類非靜態(tài)代碼塊

    子類構造方法

在這要說明下贴铜,靜態(tài)代碼塊和靜態(tài)屬性是等價的,他們是按照代碼順序執(zhí)行的瀑晒。

那么問題2的答案來了. 我們再回到問題2的代碼里面.
第一步,我們是調(diào)用了Singleton的靜態(tài)方法. 這個時候觸發(fā)了這個類是要被加載了.

首先,讀取磁盤上的class文件,各種驗證完畢,進行鏈接,準備階段給這個類的三個變量都進行初始化.
此時:

instance = null;
 a1 = 0;
 a2 = 0;

之后我們要進行初始化了,這個類么有父類.那么我們進行初始化類的靜態(tài)屬性.
第一步:
instance = new Singleton();

這個時候new Singleton()要走構造方法了.此時a1++ 之后 a1 = 1; a2 = 1;

按照順序執(zhí)行,第二步給a1賦值為1
第三部給a2賦值為0

之后我們沒有走其他步驟, 隨后打印a1與a2的值,也就是10了.


本文作者:Anderson/Jerey_Jobs

博客地址 : http://jerey.cn/

簡書地址 : Anderson大碼渣

github地址 : https://github.com/Jerey-Jobs

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绍坝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子苔悦,更是在濱河造成了極大的恐慌轩褐,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玖详,死亡現(xiàn)場離奇詭異,居然都是意外死亡劳澄,警方通過查閱死者的電腦和手機秒拔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門砂缩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來三娩,“玉大人,你說我怎么就攤上這事双吆』崆埃” “怎么了瓦宜?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵临庇,是天一觀的道長昵慌。 經(jīng)常有香客問我斋攀,道長礁芦,這世上最難降的妖魔是什么悼尾? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任闺魏,我火速辦了婚禮,結果婚禮上司草,老公的妹妹穿的比我還像新娘。我一直安慰自己埋虹,他們只是感情好娩怎,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布截亦。 她就那樣靜靜地躺著,像睡著了一般袍啡。 火紅的嫁衣襯著肌膚如雪却桶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天畴嘶,我揣著相機與錄音窗悯,去河邊找鬼偷拔。 笑死亏钩,一個胖子當著我的面吹牛姑丑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播栅哀,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼留拾,長吁一口氣:“原來是場噩夢啊……” “哼痴柔!你這毒婦竟也來了疫向?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤谈火,失蹤者是張志新(化名)和其女友劉穎糯耍,沒想到半個月后泼菌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡荒揣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年系任,在試婚紗的時候發(fā)現(xiàn)自己被綠了俩滥。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贺奠。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖挂据,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掷倔,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布勒葱,位于F島的核電站凛虽,受9級特大地震影響篮洁,放射性物質發(fā)生泄漏殃姓。R本人自食惡果不足惜袁波,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望踏幻。 院中可真熱鬧,春花似錦该面、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牵触。三九已至,卻和暖如春袜腥,著一層夾襖步出監(jiān)牢的瞬間羹令,已是汗流浹背锡宋。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工执俩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留役首,地道東北人显拜。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓远荠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親档址。 傳聞我的和親對象是個殘疾皇子守伸,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容