Java虛擬機(jī)類加載機(jī)制

虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存灵巧,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化玻褪,最終形成可以被虛擬機(jī)直接使用的Java類型许师,這就是虛擬機(jī)的類加載機(jī)制房蝉。

1.類的生命周期

類生命周期

從圖中可以看到,類的生命周期共有7個階段:加載->驗(yàn)證->準(zhǔn)備->解析->初始化->使用->卸載微渠,其中驗(yàn)證搭幻、準(zhǔn)備、解析這3個階段合起來又稱為連接階段逞盆。

虛擬機(jī)規(guī)范定義了以下4種情況必須立即對類進(jìn)行初始化:

  1. 遇到new粗卜、getstatic、putstatic纳击、invokestatic這4條字節(jié)碼指令時续扔,如果類還沒有初始化過則先對其進(jìn)行初始化。與這幾條指令對應(yīng)的場景通常有:采用new關(guān)鍵字實(shí)例化一個類的對象焕数、讀取或設(shè)置類的靜態(tài)字段(被final關(guān)鍵字修飾并且在編譯期已經(jīng)生成常量的除外)纱昧、調(diào)用類的靜態(tài)方法;
  2. 對類進(jìn)行反射調(diào)用時堡赔,如果該類還沒有初始化识脆,則先觸發(fā)其初始化;
  3. 當(dāng)對類進(jìn)行初始化時,如果發(fā)現(xiàn)其父類還沒有初始化過灼捂,則先觸發(fā)其父類的初始化离例;
  4. 當(dāng)虛擬機(jī)啟動時,會先對入口類(包含main()方法的類)進(jìn)行初始化悉稠;

以上這4種引用方式都稱為對類的主動引用宫蛆,有且只有這4種引用方式會觸發(fā)類的初始化,其他引用方式都是類的被動引用方式的猛,均不會觸發(fā)類的初始化耀盗,例如以下幾種被動引用方式:

  1. 通過子類調(diào)用父類的靜態(tài)變量、靜態(tài)方法不會觸發(fā)子類初始化
public class SuperClass {

    public static String name = "super";
    
    static {
        System.out.println("super class init...");
    }
    
    public static void callSuper() {
        System.out.println("method called in super class...");
    }
    
}
public class ChildClass extends SuperClass{

    static {
        System.out.println("child class init...");
    }
    
}

測試代碼:

    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println("print super class name: " + ChildClass.name);   
        ChildClass.callSuper();
    }

測試結(jié)果如下:

super class init...
print super class name: super
method called in super class...

這里可以看到卦尊,子類引用了父類的靜態(tài)變量叛拷,通過子類調(diào)用了父類的靜態(tài)方法,但是只觸發(fā)了父類的初始化岂却,并沒有觸發(fā)子類的初始化忿薇。對于靜態(tài)字段以及靜態(tài)方法調(diào)用,只有直接定義這個字段和方法的類才會被初始化躏哩。

  1. 對類里的常量字段進(jìn)行引用不會觸發(fā)類的初始化
public class ConstClass {

    public static int a = 10;
    
    static {
        System.out.println("ConstClass init...");
    }
}

public class ConstClass2 {

    public static final int a = 10;
    
    static {
        System.out.println("ConstClass2 init...");
    }
}

測試代碼:

    public static void main(String[] args) throws Throwable {        
        System.out.println(ConstClass.a);
        System.out.println(ConstClass2.a);      
    }

測試結(jié)果如下:

ConstClass init...
10
10

從以上結(jié)果可以看到署浩,ConstClass有觸發(fā)類的初始化,ConstClass2并沒有觸發(fā)類的初始化震庭,這是因?yàn)镃onstClass2的字段是static final修飾的瑰抵,在編譯的時候該字段值已放到常量池里了你雌,對該字段的引用僅僅是對常量池里該常量的引用器联。

  1. 通過數(shù)組定義來引用類不會觸發(fā)類的初始化
    public static void main(String[] args) throws Throwable {        
        SuperClass[] arr = new SuperClass[10];
    }

定義了一個一維數(shù)組,這樣并不會觸發(fā)類的初始化婿崭。

2.加載

  1. 通過類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流拨拓;
  2. 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu);
  3. 在Java堆中生成一個代表這個類的java.lang.Class對象氓栈,作為方法區(qū)這些數(shù)據(jù)的訪問入口渣磷;

3.驗(yàn)證

驗(yàn)證階段主要是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會危害虛擬機(jī)自身的安全授瘦。大致分為4個階段:文件格式驗(yàn)證醋界、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證提完、符號引用驗(yàn)證形纺。

4.準(zhǔn)備

準(zhǔn)備階段主要是為類變量分配內(nèi)存并設(shè)置初始值,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配徒欣。 類變量一般指static修飾的變量逐样,通常情況下初始值指的是數(shù)據(jù)類型的零值。例如:

public static int a = 10;

對于以上代碼,準(zhǔn)備階段會將變量a的值設(shè)置為初始值0而不是10脂新,設(shè)置成10是在類的初始化階段里執(zhí)行類的構(gòu)造方法<clinit>()時發(fā)生的挪捕。

public static final int a = 10;

相比之前對字段a的定義這里多了final修飾,在編譯時生成的該字段信息的屬性表中會有一個ConstantValue屬性争便,該ConstantValue的屬性值為字面量10级零,這樣在準(zhǔn)備階段會將字段a的初始值直接設(shè)置為10。

5.解析

解析是將常量池內(nèi)的符號引用轉(zhuǎn)換為直接引用的過程始花,主要包括4種解析過程:類或接口的解析妄讯、字段解析、類方法解析酷宵、接口方法解析亥贸。

  • 符號引用:以一組符號來描述所引用的目標(biāo),它與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān)浇垦,引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中炕置;
  • 直接引用:它可以是直接指向目標(biāo)的指針、相對偏移量或是一個能間接定位到目標(biāo)的句柄男韧,直接引用是與虛擬實(shí)現(xiàn)的內(nèi)存布局相關(guān)的朴摊,直接引用的目標(biāo)是一定存在于內(nèi)存中的。

兩者的區(qū)別可以簡單理解為此虑,直接引用就是對目標(biāo)對象在內(nèi)存中地址的引用甚纲,符號引用描述的只是一個字面上的邏輯關(guān)系,并不一定會反映到具體的內(nèi)存上朦前。

6.初始化

類初始化是比較關(guān)鍵的一步介杆,前面幾個步驟都是由虛擬機(jī)來主導(dǎo)執(zhí)行的,它具體是怎么執(zhí)行的我們并不清楚韭寸,到這一步才真正開始與我們寫的java代碼相關(guān)聯(lián)起來春哨。

初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程。

  • <clinit>()方法是由編譯器自動收集類中的類變量賦值動作靜態(tài)語句塊(static {}塊)中的語句合并產(chǎn)生的恩伺;
  • 虛擬機(jī)會保證子類的<clinit>()方法執(zhí)行之前赴背,父類的<clinit>()方法已經(jīng)執(zhí)行完畢。由于java.lang.Object是所有類的父類晶渠,所以肯定是java.lang.Object類的<clinit>()方法第一個執(zhí)行凰荚;
  • <clinit>()方法對于類和接口來說并不是必須的,例如類中沒有靜態(tài)語句塊褒脯,也沒有靜態(tài)變量便瑟,這樣并不一定會生成<clinit>()方法;
  • 虛擬機(jī)在執(zhí)行類的<clinit>()方法時會進(jìn)行加鎖同步憨颠,由虛擬機(jī)來保證<clinit>()方法只會執(zhí)行一次胳徽;

以下情況不會生成<clinit>()方法:
1.類中沒有任何類變量(staitc修飾的字段)以及靜態(tài)語句塊积锅;
2.類中只有static final修飾的字段,這只會在常量池中生成一個常量养盗;
3.類中雖然有類變量缚陷,但是并沒有賦值動作;

public class TestInitClass {

    public static final int A = 0;
    
    public static int B;
    
}

以上代碼編譯時并不會生成<clinit>()方法往核,字段“A”是static final修飾的箫爷,會在編譯時生成一個ConstantValue常量,字段“B”雖然不會生成常量聂儒,但是代碼里并沒有對其進(jìn)行賦值虎锚,所以編譯時并不會生成<clinit>()方法。
典型的會生成<clinit>()方法的代碼如下:

public class TestInitClass {

    public static int A = 0;

    static {
        System.out.println("");
    }
    
}

7.接口的初始化

看到相關(guān)資料說衩婚,接口類在編譯時也會生成<clinit>()方法窜护。首先關(guān)于接口類有2點(diǎn)必須先清楚:1.接口類里不能定義靜態(tài)語句塊;2.接口類里的字段會被自動轉(zhuǎn)為public static final類型的非春,不管代碼里有沒有為變量定義public static final這幾個關(guān)鍵字柱徙。鑒于這2點(diǎn),本人對接口類也會生成<clinit>()方法存疑奇昙,前面說明過這種情況下并不會生成<clinit>()方法护侮,本著懷疑的態(tài)度自己編寫一個測試類來看看:

public interface TestInterface {  

    int b = 10;
    
    void test();
}

對該接口采用javac命令編譯后再用javap -v TestInterface.class命令查看字節(jié)碼信息:

public interface TestInterface
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
   #1 = Class              #11            // TestInterface
   #2 = Class              #12            // java/lang/Object
   #3 = Utf8               b
   #4 = Utf8               I
   #5 = Utf8               ConstantValue
   #6 = Integer            10
   #7 = Utf8               test
   #8 = Utf8               ()V
   #9 = Utf8               SourceFile
  #10 = Utf8               TestInterface.java
  #11 = Utf8               TestInterface
  #12 = Utf8               java/lang/Object
{
  public static final int b;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 10

  public abstract void test();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "TestInterface.java"

從上面可以看出:
1.雖然代碼里我們定義的是“int b”,但是編譯后的字節(jié)碼里“b”的實(shí)際類型是“public static final int b”储耐;
2.這里并沒有<clinit>()方法生成羊初;

這僅僅是個人的見解,并不一定正確什湘,歡迎批評指正长赞。

java類加載機(jī)制系列文章:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市禽炬,隨后出現(xiàn)的幾起案子涧卵,更是在濱河造成了極大的恐慌勤家,老刑警劉巖腹尖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伐脖,居然都是意外死亡热幔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門讼庇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绎巨,“玉大人,你說我怎么就攤上這事蠕啄〕∏冢” “怎么了戈锻?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長险胰,這世上最難降的妖魔是什么舔痪? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮施符,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己璧微,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布硬梁。 她就那樣靜靜地躺著前硫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荧止。 梳的紋絲不亂的頭發(fā)上开瞭,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機(jī)與錄音罩息,去河邊找鬼嗤详。 笑死,一個胖子當(dāng)著我的面吹牛瓷炮,可吹牛的內(nèi)容都是我干的葱色。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼娘香,長吁一口氣:“原來是場噩夢啊……” “哼苍狰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起烘绽,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤淋昭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后安接,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翔忽,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年盏檐,在試婚紗的時候發(fā)現(xiàn)自己被綠了歇式。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡胡野,死狀恐怖材失,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情硫豆,我是刑警寧澤龙巨,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布笼呆,位于F島的核電站,受9級特大地震影響旨别,放射性物質(zhì)發(fā)生泄漏抄邀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一昼榛、第九天 我趴在偏房一處隱蔽的房頂上張望境肾。 院中可真熱鬧,春花似錦胆屿、人聲如沸奥喻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽环鲤。三九已至,卻和暖如春憎兽,著一層夾襖步出監(jiān)牢的瞬間冷离,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工纯命, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留西剥,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓亿汞,卻偏偏與公主長得像瞭空,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子疗我,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評論 2 359

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

  • 讀書筆記 深入理解Java虛擬機(jī):JVM高級特性與最佳實(shí)現(xiàn)(第二版) 概述 深入了解了Class文件存儲格式的具...
    Bollen_Chak閱讀 474評論 0 1
  • 什么叫類加載 JVM 將Class文件加載到內(nèi)存咆畏,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化吴裤,最終形成可以被虛擬機(jī)直接使用...
    那些年未曾努力過閱讀 408評論 0 0
  • Java程序運(yùn)行于Java虛擬機(jī)之上旧找,JVM屏蔽了底層細(xì)節(jié),使得Java程序能夠“一次編譯麦牺,到處運(yùn)行”钮蛛。在Java...
    程序之心閱讀 328評論 0 8
  • Java類加載的過程 類從被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止枕面,它的整個生命周期包括:加載愿卒、驗(yàn)證缚去、準(zhǔn)備潮秘、解析...
    hbh404閱讀 291評論 0 0
  • 老師寫在前面的話: 昨天晚上我正在準(zhǔn)備下周一的課,一個學(xué)生家長打電話給我易结,嘮叨孩子的不聽話枕荞,說孩子如何氣人柜候。首先,...
    夏花靜秋閱讀 332評論 1 1