JVM類加載的那些事

簡書 占小狼
轉(zhuǎn)載請注明原創(chuàng)出處夺荒,謝謝!

前言

Java源代碼被編譯成class字節(jié)碼良蒸,最終需要加載到虛擬機(jī)中才能運(yùn)行技扼。整個生命周期包括:加載、驗(yàn)證嫩痰、準(zhǔn)備剿吻、解析、初始化始赎、使用和卸載7個階段和橙。


加載

1、通過一個類的全限定名獲取描述此類的二進(jìn)制字節(jié)流造垛;
2魔招、將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)保存為方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu);
3五辽、在java堆中生成一個代表這個類的java.lang.Class對象办斑,作為訪問方法區(qū)的入口;

虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把加載動作放到JVM外部實(shí)現(xiàn)杆逗,以便讓應(yīng)用程序決定如何獲取所需的類乡翅,實(shí)現(xiàn)這個動作的代碼稱為“類加載器”,JVM提供了3種類加載器:
1罪郊、啟動類加載器(Bootstrap ClassLoader):負(fù)責(zé)加載 JAVA_HOME\lib 目錄中的蠕蚜,或通過-Xbootclasspath參數(shù)指定路徑中的,且被虛擬機(jī)認(rèn)可(按文件名識別悔橄,如rt.jar)的類靶累。
2、擴(kuò)展類加載器(Extension ClassLoader):負(fù)責(zé)加載 JAVA_HOME\lib\ext 目錄中的癣疟,或通過java.ext.dirs系統(tǒng)變量指定路徑中的類庫挣柬。
3、應(yīng)用程序類加載器(Application ClassLoader):負(fù)責(zé)加載用戶路徑(classpath)上的類庫睛挚。

JVM基于上述類加載器邪蛔,通過雙親委派模型進(jìn)行類的加載,當(dāng)然我們也可以通過繼承java.lang.ClassLoader實(shí)現(xiàn)自定義的類加載器扎狱。


雙親委派模型工作過程:當(dāng)一個類加載器收到類加載任務(wù)侧到,優(yōu)先交給其父類加載器去完成勃教,因此最終加載任務(wù)都會傳遞到頂層的啟動類加載器,只有當(dāng)父類加載器無法完成加載任務(wù)時床牧,才會嘗試執(zhí)行加載任務(wù)荣回。

雙親委派模型有什么好處?
比如位于rt.jar包中的類java.lang.Object戈咳,無論哪個加載器加載這個類,最終都是委托給頂層的啟動類加載器進(jìn)行加載壕吹,確保了Object類在各種加載器環(huán)境中都是同一個類著蛙。

驗(yàn)證

為了確保Class文件符合當(dāng)前虛擬機(jī)要求,需要對其字節(jié)流數(shù)據(jù)進(jìn)行驗(yàn)證耳贬,主要包括格式驗(yàn)證踏堡、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證和符號引用驗(yàn)證咒劲。

  1. 格式驗(yàn)證
    驗(yàn)證字節(jié)流是否符合class文件格式的規(guī)范顷蟆,并且能被當(dāng)前虛擬機(jī)處理,如是否以魔數(shù)0xCAFEBABE開頭腐魂、主次版本號是否在當(dāng)前虛擬機(jī)處理范圍內(nèi)帐偎、常量池是否有不支持的常量類型等。只有經(jīng)過格式驗(yàn)證的字節(jié)流蛔屹,才會存儲到方法區(qū)的數(shù)據(jù)結(jié)構(gòu)削樊,剩余3個驗(yàn)證都基于方法區(qū)的數(shù)據(jù)進(jìn)行。
  • 元數(shù)據(jù)驗(yàn)證
    對字節(jié)碼描述的數(shù)據(jù)進(jìn)行語義分析兔毒,以保證符合Java語言規(guī)范漫贞,如是否繼承了final修飾的類、是否實(shí)現(xiàn)了父類的抽象方法育叁、是否覆蓋了父類的final方法或final字段等迅脐。
  • 字節(jié)碼驗(yàn)證
    對類的方法體進(jìn)行分析,確保在方法運(yùn)行時不會有危害虛擬機(jī)的事件發(fā)生豪嗽,如保證操作數(shù)棧的數(shù)據(jù)類型和指令代碼序列的匹配谴蔑、保證跳轉(zhuǎn)指令的正確性、保證類型轉(zhuǎn)換的有效性等昵骤。
  • 符號引用驗(yàn)證
    為了確保后續(xù)的解析動作能夠正常執(zhí)行树碱,對符號引用進(jìn)行驗(yàn)證,如通過字符串描述的全限定名是都能找到對應(yīng)的類变秦、在指定類中是否存在符合方法的字段描述符等成榜。

準(zhǔn)備

在準(zhǔn)備階段,為類變量(static修飾)在方法區(qū)中分配內(nèi)存并設(shè)置初始值蹦玫。

private static int var = 100;

準(zhǔn)備階段完成后赎婚,var 值為0刘绣,而不是100。在初始化階段挣输,才會把100賦值給val纬凤,但是有個特殊情況:

private static final int VAL= 100;

在編譯階段會為VAL生成ConstantValue屬性,在準(zhǔn)備階段虛擬機(jī)會根據(jù)ConstantValue屬性將VAL賦值為100撩嚼。

解析

解析階段是將常量池中的符號引用替換為直接引用的過程停士,符號引用和直接引用有什么不同?
1完丽、符號引用使用一組符號來描述所引用的目標(biāo)恋技,可以是任何形式的字面常量,定義在Class文件格式中逻族。
2蜻底、直接引用可以是直接指向目標(biāo)的指針、相對偏移量或則能間接定位到目標(biāo)的句柄聘鳞。

初始化

初始化階段是執(zhí)行類構(gòu)造器<clinit>方法的過程薄辅,<clinit>方法由類變量的賦值動作和靜態(tài)語句塊按照在源文件出現(xiàn)的順序合并而成,該合并操作由編譯器完成抠璃。

    private static int value = 100;
    static int a = 100;
    static int b = 100;
    static int c;

    static {
        c = a + b;
        System.out.println("it only run once");
    }

1站楚、<clinit>方法對于類或接口不是必須的,如果一個類中沒有靜態(tài)代碼塊鸡典,也沒有靜態(tài)變量的賦值操作源请,那么編譯器不會生成<clinit>;
2彻况、<clinit>方法與實(shí)例構(gòu)造器不同谁尸,不需要顯式的調(diào)用父類的<clinit>方法,虛擬機(jī)會保證父類的<clinit>優(yōu)先執(zhí)行纽甘;
3良蛮、為了防止多次執(zhí)行<clinit>驯击,虛擬機(jī)會確保<clinit>方法在多線程環(huán)境下被正確的加鎖同步執(zhí)行弥鹦,如果有多個線程同時初始化一個類,那么只有一個線程能夠執(zhí)行<clinit>方法芍瑞,其它線程進(jìn)行阻塞等待左权,直到<clinit>執(zhí)行完成皮胡。
4、注意:執(zhí)行接口的<clinit>方法不需要先執(zhí)行父接口的<clinit>赏迟,只有使用父接口中定義的變量時屡贺,才會執(zhí)行。

類初始化場景

虛擬機(jī)中嚴(yán)格規(guī)定了有且只有5種情況必須對類進(jìn)行初始化。

  • 執(zhí)行new甩栈、getstatic泻仙、putstatic和invokestatic指令;
  • 使用reflect對類進(jìn)行反射調(diào)用量没;
  • 初始化一個類的時候玉转,父類還沒有初始化,會事先初始化父類殴蹄;
  • 啟動虛擬機(jī)時究抓,需要初始化包含main方法的類;
  • 在JDK1.7中袭灯,如果java.lang.invoke.MethodHandler實(shí)例最后的解析結(jié)果REF_getStatic漩蟆、REF_putStatic、REF_invokeStatic的方法句柄妓蛮,并且這個方法句柄對應(yīng)的類沒有進(jìn)行初始化;

以下幾種情況圾叼,不會觸發(fā)類初始化
1蛤克、通過子類引用父類的靜態(tài)字段,只會觸發(fā)父類的初始化夷蚊,而不會觸發(fā)子類的初始化构挤。

class Parent {
    static int a = 100;
    static {
        System.out.println("parent init!");
    }
}

class Child extends Parent {
    static {
        System.out.println("child init惕鼓!");
    }
}

public class Init{  
    public static void main(String[] args){  
        System.out.println(Child.a);  
    }  
}  

輸出結(jié)果為:
parent init筋现!
100

2、定義對象數(shù)組箱歧,不會觸發(fā)該類的初始化矾飞。

public class Init{  
    public static void main(String[] args){  
        Parent[] parents = new Parent[10];
    }  
}  

無輸出,說明沒有觸發(fā)類Parent的初始化呀邢,但是這段代碼做了什么洒沦?先看看生成的字節(jié)碼指令


anewarray指令為新數(shù)組分配空間,并觸發(fā)[Lcom.ctrip.ttd.whywhy.Parent類的初始化价淌,這個類由虛擬機(jī)自動生成申眼。

3、常量在編譯期間會存入調(diào)用類的常量池中蝉衣,本質(zhì)上并沒有直接引用定義常量的類括尸,不會觸發(fā)定義常量所在的類。

class Const {
    static final int A = 100;
    static {
        System.out.println("Const init");
    }
}

public class Init{  
    public static void main(String[] args){  
        System.out.println(Const.A);  
    }  
}  

輸出:
100
說明沒有觸發(fā)類Const的初始化病毡,在編譯階段濒翻,Const類中常量A的值100存儲到Init類的常量池中,這兩個類在編譯成class文件之后就沒有聯(lián)系了。

4肴焊、通過類名獲取Class對象前联,不會觸發(fā)類的初始化。

public class test {
   public static void main(String[] args) throws ClassNotFoundException {
        Class c_dog = Dog.class;
        Class clazz = Class.forName("zzzzzz.Cat");
    }
}

class Cat {
    private String name;
    private int age;
    static {
        System.out.println("Cat is load");
    }
}

class Dog {
    private String name;
    private int age;
    static {
        System.out.println("Dog is load");
    }
}

執(zhí)行結(jié)果:Cat is load娶眷,所以通過Dog.class并不會觸發(fā)Dog類的初始化動作似嗤。

5、通過Class.forName加載指定類時届宠,如果指定參數(shù)initialize為false時烁落,也不會觸發(fā)類初始化,其實(shí)這個參數(shù)是告訴虛擬機(jī)豌注,是否要對類進(jìn)行初始化伤塌。

public class test {
   public static void main(String[] args) throws ClassNotFoundException {
        Class clazz = Class.forName("zzzzzz.Cat", false, Cat.class.getClassLoader());
    }
}
class Cat {
    private String name;
    private int age;
    static {
        System.out.println("Cat is load");
    }
}

6、通過ClassLoader默認(rèn)的loadClass方法轧铁,也不會觸發(fā)初始化動作

new ClassLoader(){}.loadClass("zzzzzz.Cat");

END每聪。
我是占小狼。
在魔都艱苦奮斗齿风,白天是上班族药薯,晚上是知識服務(wù)工作者。
如果讀完覺得有收獲的話救斑,記得關(guān)注和點(diǎn)贊哦童本。
非要打賞的話,我也是不會拒絕的脸候。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末穷娱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子运沦,更是在濱河造成了極大的恐慌泵额,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茶袒,死亡現(xiàn)場離奇詭異梯刚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)薪寓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門亡资,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人向叉,你說我怎么就攤上這事锥腻。” “怎么了母谎?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵瘦黑,是天一觀的道長。 經(jīng)常有香客問我,道長幸斥,這世上最難降的妖魔是什么匹摇? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮甲葬,結(jié)果婚禮上廊勃,老公的妹妹穿的比我還像新娘。我一直安慰自己经窖,他們只是感情好坡垫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著画侣,像睡著了一般冰悠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上配乱,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天溉卓,我揣著相機(jī)與錄音,去河邊找鬼搬泥。 笑死的诵,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的佑钾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼烦粒,長吁一口氣:“原來是場噩夢啊……” “哼休溶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起扰她,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤兽掰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后徒役,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孽尽,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年忧勿,在試婚紗的時候發(fā)現(xiàn)自己被綠了杉女。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸳吸,死狀恐怖熏挎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晌砾,我是刑警寧澤坎拐,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響哼勇,放射性物質(zhì)發(fā)生泄漏都伪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一积担、第九天 我趴在偏房一處隱蔽的房頂上張望陨晶。 院中可真熱鬧,春花似錦磅轻、人聲如沸珍逸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谆膳。三九已至,卻和暖如春撮躁,著一層夾襖步出監(jiān)牢的瞬間漱病,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工把曼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留杨帽,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓嗤军,卻偏偏與公主長得像注盈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子叙赚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354

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