關(guān)于JVM我們必須要知道的知識點(二)

在上篇文章中棒呛,我們介紹了運行時數(shù)據(jù)區(qū)域、垃圾回收算法域携、Java引用分類等一些有關(guān)于JVM的知識簇秒,今天我們一起來學(xué)習(xí)一下JVM類加載機(jī)制相關(guān)的部分。


定義:虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存秀鞭,并對數(shù)據(jù)進(jìn)行校驗趋观、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型锋边,這就是虛擬機(jī)的類加載機(jī)制皱坛。

類從被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止宠默,它的整個生命周期可分為7個階段麸恍,分別為:加載、驗證搀矫、準(zhǔn)備抹沪、解析、初始化瓤球、使用融欧、卸載。如下圖所示:

類的生命周期

對于加載的時機(jī)卦羡,JVM沒有做強(qiáng)制約束噪馏,但是對于初始化階段,虛擬機(jī)規(guī)范則是嚴(yán)格規(guī)定了有且只有以下5種情況必須立即對類進(jìn)行“初始化”操作:

(1)遇到new绿饵、getstatic欠肾、putstatic或invokestatic這四條字節(jié)碼指令時,如果類沒有進(jìn)行初始化拟赊,則需要先觸發(fā)其初始化刺桃。常見的場景有:使用new關(guān)鍵字實例化對象的時候、讀取或設(shè)置一個類的靜態(tài)字段(被final修飾吸祟、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)瑟慈,以及調(diào)用一個類的靜態(tài)方法的時候桃移。

(2)使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用的時候,如果類沒有進(jìn)行過初始化葛碧,則需要先觸發(fā)其初始化借杰。

(3)當(dāng)初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化进泼,則需要先觸發(fā)其父類的初始化蔗衡。

(4)當(dāng)虛擬機(jī)啟動時,用戶需要指定一個要執(zhí)行的主類(包含main()方法的那個類)缘琅,虛擬機(jī)會先初始化這個主類粘都。

(5)當(dāng)使用JDK1.7的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結(jié)果REF_getStatic刷袍、REF_putStatic、REF_invokeStatic的方法句柄樊展,并且這個方法句柄所對應(yīng)的類沒有進(jìn)行過初始化呻纹,則需要先觸發(fā)其初始化。

上述5種場景中的行為专缠,會觸發(fā)類的初始化雷酪,稱之為主動引用。除此之外涝婉,還有一些引用方式不會導(dǎo)致類的初始化哥力,稱為被動引用。常見的被動引用操作有:

(1)通過子類引用父類的靜態(tài)字段墩弯,不會導(dǎo)致子類初始化吩跋。

(2)通過數(shù)組定義來引用類,不會觸發(fā)此類的初始化渔工。

(3)常量在編譯階段會存入調(diào)用類的常量池中锌钮,本質(zhì)上并沒有直接引用到定義常量的類,因此不會觸發(fā)定義常量的類的初始化引矩。


1. 加載

在加載階段梁丘,虛擬機(jī)主要完成以下3件事情:

(1)通過一個類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。

(2)將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)旺韭。

(3)在內(nèi)存中生成一個代表這個類的java.lang.Class對象氛谜,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。

2.驗證

驗證階段的目的:確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求区端,并且不會危害虛擬機(jī)自身的安全值漫。

驗證階段大體可分為四個方面,分別為:文件格式驗證珊燎、元數(shù)據(jù)驗證惭嚣、字節(jié)碼驗證遵湖、符號引用驗證。

3.準(zhǔn)備

準(zhǔn)備階段的作用:為類變量(被static修飾的變量)分配內(nèi)存并且設(shè)置類變量的初始值(數(shù)據(jù)類型零值)晚吞。

4.解析

解析階段的作用:虛擬機(jī)將常量池中的符號引用替換為直接引用延旧。包括有:類或接口的解析、字段解析槽地、類方法解析迁沫、接口方法解析等。

5.初始化

初始化階段:真正開始執(zhí)行類中定義的Java程序代碼捌蚊,初始化類變量和其他資源集畅。或者可以從另一個角度來表達(dá):初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程缅糟。

注:<clinit>()方法:由編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊(static{}塊)中的語句合并產(chǎn)生挺智。


類加載器(ClassLoader)

關(guān)于類加載器的知識,想必大家已經(jīng)很熟悉了窗宦,在這里我還是要再提一下赦颇。類加載器作用于類生命周期的加載階段,主要作用可以表述為:將.class文件(字節(jié)碼文件)加載到JVM中赴涵,并轉(zhuǎn)換成相應(yīng)的Class對象媒怯,供JVM識別和使用。

在講解類加載器之前髓窜,我們先明確一點:對于任意一個類扇苞,都需要由加載它的類加載器和這個類本身一同確定其在Java虛擬機(jī)中的唯一性。通俗來講就是:比較兩個類是否“相等”寄纵,只有在這兩個類是由同一加載器加載的前提下才有意義鳖敷,否則,即使這兩個類來源于同一個Class文件擂啥,被同一個虛擬機(jī)加載哄陶,只要加載它們的類加載器不同,那這兩個類就必定不相等哺壶。

Java語言為我們提供了三種類加載器屋吨,分別為:Bootstrap ClassLoader(啟動類加載器)、Extension ClassLoader(擴(kuò)展類加載器)和Application ClassLoader(應(yīng)用程序類加載器)山宾。

Bootstrap ClassLoader(啟動類加載器):使用C++語言實現(xiàn)至扰,是虛擬機(jī)自身的一部分。負(fù)責(zé)加載<JAVA_HOME>\lib目錄下或者被-Xbootclasspath參數(shù)指定路徑下的rt.jar资锰、resources.jar敢课、charsets.jar、.class等。

Extension ClassLoader(擴(kuò)展類加載器):由Java語言實現(xiàn)直秆,獨立于虛擬機(jī)外部濒募。負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄下或者被java.ext.dirs系統(tǒng)變量所指定路徑下的所有類庫。

Application ClassLoader(應(yīng)用程序類加載器圾结、系統(tǒng)類加載器):由Java語言實現(xiàn)瑰剃,獨立于虛擬機(jī)外部。負(fù)責(zé)加載當(dāng)前應(yīng)用的ClassPath目錄下的所有類筝野。

簡單來講晌姚,上述三種類加載器最大的不同之處就是它們所負(fù)責(zé)加載的目錄不同。如果有需要的話歇竟,我們還可以自定義類加載器挥唠,比如從網(wǎng)絡(luò)獲取.class文件并加載到JVM中。

談到類加載器焕议,自然離不開“雙親委派機(jī)制”這個老生常談的話題宝磨,下面我們一起來看一下。

類加載器雙親委派機(jī)制

雙親委派機(jī)制要求除了頂層的啟動類加載器之外盅安,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器懊烤。在這里我們要明確一點,上述類加載器之間的關(guān)系并非繼承關(guān)系宽堆。

雙親委派機(jī)制的工作過程:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類茸习,而是把這個請求委派給父類加載器去完成畜隶,每一個層次的類加載器都是如此,因此所有的加載請求最終傳送到頂層的啟動類加載器号胚,只有當(dāng)父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時籽慢,子加載器才會自己嘗試去加載。

使用雙親委派機(jī)制的好處:保證了Java核心類庫的安全猫胁。它使得Java 類隨著它的類加載器一起具備了一種帶優(yōu)先級的層次關(guān)系箱亿。例如 java.lang.Object 類,無論哪個類加載器去加載該類弃秆,最終都由啟動類加載器加載届惋,因此Object 類在程序的各種類加載器環(huán)境中都是同一個類。相反菠赚,如果沒有使用雙親委派機(jī)制脑豹,由各個類加載器去自行加載的話,用戶自定義一個java.lang.Object 類放在Classpath 目錄下衡查,那么系統(tǒng)中會出現(xiàn)多個不同的Object類瘩欺,導(dǎo)致程序一片混亂。


上面噼里啪啦說了這么多,下面我們一起來驗證一下俱饿,這樣更有助于我們理解和記憶歌粥。

在這里,我們創(chuàng)建一個Person類拍埠,然后在main方法中打印出Person類對應(yīng)的ClassLoader失驶。如圖所示:

打印classloader

運行結(jié)果如下所示:

運行結(jié)果

在這里我們可以看出,平常我們在程序中定義的類是由AppClassLoader加載的械拍。根據(jù)上文中的雙親委托機(jī)制突勇,我們了解到AppClassloader的父加載器為ExtClassLoader,那AppClassloader和ExtClassLoader在代碼中具體有什么關(guān)聯(lián)呢坷虑,我們通過ClassLoader的getParent()方法來看一下:

程序及驗證結(jié)果

可以看到甲馋,我們在AppClassloader中調(diào)用了getParent方法,得到了ExtClassLoader對象迄损。我相信你肯定會對它的源碼感興趣定躏。

Launcher.class

由上述源碼我們可以知道,AppClassloader和ExtClassLoader都是定義在Launcher中的靜態(tài)內(nèi)部類芹敌,并且兩者都繼承自URLClassLoader痊远。而URLClassLoader最終又是繼承自ClassLoader類。這也驗證了上文中我們所說的雙親委托機(jī)制并非繼承關(guān)系氏捞,而是一種組合關(guān)系碧聪。

Launcher是什么鬼東西呢?它其實是一個java虛擬機(jī)的入口應(yīng)用液茎,我們要想搞清楚AppClassloader的父加載器為什么是ExtClassLoader逞姿,還需要從Launcher的構(gòu)造函數(shù)查找出答案。

Launcher構(gòu)造函數(shù)

在這里我只截取了一部分代碼捆等,大家需要注意的地方就是我打斷點的那兩行代碼滞造。先大致說一下,第一行代碼的作用就是創(chuàng)建了ExtClassLoader對象栋烤,第二行代碼的作用就是創(chuàng)建AppClassloader對象谒养,并在構(gòu)造方法中將之前生成的ExtClassLoader對象作為參數(shù)傳進(jìn)去。在這里我們可以大膽猜想一下明郭,在AppClassLoader中肯定定義了一個parent變量买窟,用來接收getAppClassLoader方法中傳入的參數(shù)。到底是不是這樣呢达址?我們拭目以待蔑祟。

我們跟進(jìn)去getAppClassLoader方法去看一下,結(jié)果如下:

getAppClassLoader方法

可以看到沉唠,getAppClassLoader方法中最終又是調(diào)用到AppClassLoader中帶有兩個參數(shù)的構(gòu)造函數(shù)疆虚,分別將加載路徑和我們傳過來的classloader(也就是ExtClassLoader對象)傳入,我們接著跟進(jìn)去圖中 2 標(biāo)注的super方法,最終會調(diào)用到ClassLoader類中帶有兩個參數(shù)的構(gòu)造方法径簿,其中var2參數(shù)也就是我們傳過來的ExtClassLoader對象罢屈。

ClassLoader類中帶有兩個參數(shù)的構(gòu)造方法

注意看最后一行我用紅筆標(biāo)注的地方,到這里真相大白了吧篇亭。在我們創(chuàng)建AppClassloader對象的時候缠捌,會將之前創(chuàng)建的ExtClassLoader對象作為參數(shù)傳入,在AppClassloader類中定義了一個parent變量译蒂,用來接收我們傳入的ExtClassLoader對象曼月。

那么我們接下來再看一下,ExtClassLoader和BootsTrapClassLoader在代碼中有什么關(guān)聯(lián)呢柔昼?

程序及運行結(jié)果

由運行結(jié)果我們可以看到哑芹,程序拋出了空指針異常。我們判斷肯定是13行調(diào)用了toString方法導(dǎo)致的捕透,這也就說明ExtClassLoader的getParent方法為null聪姿。

what?乙嘀?末购?are you kidding me?虎谢?盟榴?

要想確認(rèn)我們的推斷,我默默的翻起了源碼婴噩。

Launcher構(gòu)造函數(shù)

剛才我們只是分析了斷點第二行曹货,也就是AppClassLoader的創(chuàng)建過程,接下來我們照例分析下斷點第一行ExtClassLoader的創(chuàng)建過程讳推。在這里我們要注意下,調(diào)用getExtClassLoader方法并沒有傳參數(shù)玩般。我們跟進(jìn)去看下:

getExtClassLoader方法

我們可以看出getExtClassLoader方法最終又是調(diào)用到ExtClassLoader一個參數(shù)的構(gòu)造方法银觅,將加載路徑傳入,我們接下來看下ExtClassLoader一個參數(shù)的構(gòu)造方法坏为。

ExtClassLoader一個參數(shù)的構(gòu)造方法

睜大眼睛注意了究驴,方法中調(diào)用super類時,傳入的第二個參數(shù)匀伏,也就是ClassLoader對象為null洒忧。接下來的調(diào)用就和AppClassLoader類似了,最終會調(diào)用到ClassLoader類中帶有兩個參數(shù)的構(gòu)造方法够颠,其中var2參數(shù)也就是我們傳過來的null熙侍。

ClassLoader類中帶有兩個參數(shù)的構(gòu)造方法

這下我們確信了,ExtClassLoader中的parent參數(shù)確定為null。

接下來我們來看下雙親委托機(jī)制的代碼實現(xiàn)(代碼有所刪減):

雙親委托機(jī)制的代碼實現(xiàn)

在我們當(dāng)前ClassLoader進(jìn)行類加載的時候會調(diào)用到loadClass方法蛉抓。方法中首先判斷所要加載的類是否已經(jīng)被加載過庆尘,如果已經(jīng)被加載過了,則直接返回我們加載過的類對象巷送。如果沒有加載過驶忌,則判斷當(dāng)前ClassLoader的parent參數(shù)是否為null,關(guān)于parent參數(shù)的賦值笑跛,我們在上文中已經(jīng)通過源碼分析過了付魔,簡單講,AppClassloader的parent參數(shù)為ExtClassLoader對象飞蹂,而ExtClassLoader的parent參數(shù)為null几苍。例如我們當(dāng)前的ClassLoader為AppClassloader,則parent參數(shù)不為null晤柄,會調(diào)用ExtClassLoader的loadClass方法進(jìn)行類加載擦剑,即AppClassloader的父加載器為ExtClassLoader。例如我們當(dāng)前的ClassLoader為ExtClassLoader芥颈,則parent參數(shù)為null惠勒,會調(diào)用BootstrapClassLoader進(jìn)行類加載,即ExtClassLoader的父加載器為BootstrapClassloader爬坑。在父加載器無法完成類加載的時候纠屋,最終會調(diào)用到當(dāng)前ClassLoader的findClass方法進(jìn)行類加載。


關(guān)于JVM中類加載機(jī)制相關(guān)的知識我們先講到這里盾计,希望在我學(xué)習(xí)的同時也有幫助到大家售担。如果有哪些分析不對的地方,還望老哥們指出署辉,我后續(xù)改正過來族铆。后續(xù)我會接著分析下Android中類加載機(jī)制相關(guān)部分,歡迎大家關(guān)注哭尝。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哥攘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子材鹦,更是在濱河造成了極大的恐慌逝淹,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桶唐,死亡現(xiàn)場離奇詭異栅葡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)尤泽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門欣簇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來规脸,“玉大人,你說我怎么就攤上這事醉蚁∪枷剑” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵网棍,是天一觀的道長黔龟。 經(jīng)常有香客問我,道長滥玷,這世上最難降的妖魔是什么氏身? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮惑畴,結(jié)果婚禮上蛋欣,老公的妹妹穿的比我還像新娘。我一直安慰自己如贷,他們只是感情好陷虎,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杠袱,像睡著了一般尚猿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上楣富,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天凿掂,我揣著相機(jī)與錄音,去河邊找鬼纹蝴。 笑死庄萎,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的塘安。 我是一名探鬼主播糠涛,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼兼犯!你這毒婦竟也來了脱羡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤免都,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后帆竹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绕娘,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡咐汞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年访敌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戒职。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绢陌,靈堂內(nèi)的尸體忽然破棺而出挨下,到底是詐尸還是另有隱情,我是刑警寧澤脐湾,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布臭笆,位于F島的核電站,受9級特大地震影響秤掌,放射性物質(zhì)發(fā)生泄漏愁铺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一闻鉴、第九天 我趴在偏房一處隱蔽的房頂上張望茵乱。 院中可真熱鬧,春花似錦孟岛、人聲如沸瓶竭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斤贰。三九已至,卻和暖如春堵未,著一層夾襖步出監(jiān)牢的瞬間腋舌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工渗蟹, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留块饺,地道東北人。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓雌芽,卻偏偏與公主長得像授艰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子世落,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354

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