類加載的過程:加載、驗證爆安、準(zhǔn)備叛复、解析、初始化,一個類的生命周期

類的生命周期概述

Java程序的所有數(shù)據(jù)結(jié)構(gòu)和算法都封裝在類型之中扔仓,這也是面向?qū)ο缶幊陶Z言的一大特色褐奥。當(dāng)JVM執(zhí)行一個Java類所封裝的算法之前 ,首先要做的一件事便是字節(jié)碼文件解析翘簇,字節(jié)碼文件解析包含 3 個主要的過程:常量池解析撬码、Java類字段解析及 Java 方法解析。通過類字段解析版保,JVM能夠分析出Java類所封裝的數(shù)據(jù)結(jié)構(gòu)呜笑;通過方法解析,JVM能夠分析出Java類所封裝的算法邏輯 彻犁。而無論是數(shù)據(jù)結(jié)構(gòu)還是方法信息叫胁,很多與“字符串”或者大數(shù)據(jù)(是指占二進(jìn)位比較多的大數(shù))相關(guān)的信息都封裝于常量池中,所以JVM欲解析字段和方法信息袖裕,必先解析常量池曹抬。當(dāng)常量池、字段和方法信息全部被解析完急鳄,則字節(jié)碼文件的“精華”便已經(jīng)被完全消化吸收谤民。但是,這幾個過程其實僅僅屬于Java類 “加載”過程中的一個環(huán)節(jié)疾宏,這對于一個Java類的整個“漫長”的生命周期而言张足,僅僅是個開始。在字節(jié)碼文件的精華被吸收之后還需要經(jīng)過一系列的“二次” 消化處理坎藐,方能被JVM在運(yùn)行期“隨心所欲”地調(diào)用为牍。

按照J(rèn)VM規(guī)范,一個Java文件從被加載到被卸載的整個生命過程岩馍,總共要經(jīng)歷5個階段 : 加載?→?鏈接(驗證+準(zhǔn)備+解析)→?初始化(使用前的準(zhǔn)備)→?使用?→?卸載碉咆。其中第二個階段“鏈接”,對應(yīng)了3個階段 :驗證 蛀恩、準(zhǔn)備和解析疫铜,因此,也有很多典籍說 Java 類的生命周期一共包括7個階段双谆。

前文所講的常量池解析壳咕、Java字段和方法的解析席揽,其實都屬于加載階段的一部分。所謂加 載谓厘,簡而言之就是將 Java 類的字節(jié)碼文件加載到機(jī)器內(nèi)存中并在內(nèi)存中構(gòu)建出Java類的原型類模板對象幌羞。所謂類模板對象,其實就是 Java類在JVM內(nèi)存中的一個快照竟稳,JVM將從字節(jié)碼文件中解析出的常量池属桦、類字段、類方法等信息存儲到類模板中他爸,這樣JVM在運(yùn)行期便能通過類模板而獲取Java類中的任意信息地啰,能夠?qū)?Java 類的成員變量進(jìn)行遍歷,也能進(jìn)行 Java 方法的調(diào)用讲逛。反射的機(jī)制即基于這一基礎(chǔ)。如果 JVM 沒有將 Java 類的聲明信息存儲起來岭埠,則只叫在運(yùn)行期也無法反射盏混。字節(jié)碼相關(guān)的工具類庫,例如 asm 惜论、cglib 等许赃,都利用了這一機(jī)制,在運(yùn)行期動態(tài)修改靜態(tài)聲明的 Java 類所對應(yīng)的字節(jié)碼內(nèi)容馆类,從而在運(yùn)行期直接改掉Java 類的定義混聊,甚至直接在運(yùn)行期創(chuàng)建一個全新的Java類。

Java類是寫給人類看的乾巧,而只叫內(nèi)存中的類模板快照則是寫給機(jī)器“看”的句喜。物理機(jī)器無法直接執(zhí)行Java類的源代碼,所以需要通過類加載這樣一個過程將字節(jié)碼格式的 Java 類轉(zhuǎn)換成機(jī)器能夠識別的內(nèi)存類模板快照沟于。

JVM完成 Java 類加載之后咳胃,接著便開始進(jìn)行鏈接。所謂鏈接旷太,雖然與編譯原理中的鏈接不是同一件事展懈,然而本質(zhì)上是相同的」╄担總體而言存崖,鏈接的主要作用是將字節(jié)碼指令中對常量池中的索引引用轉(zhuǎn)換為直接引用。鏈接包含 3 個步驟 :驗證睡毒、準(zhǔn)備和解析来惧。其實在類加載階段(也即類的生命周期的第一個階段)JVM會對字節(jié)碼文件進(jìn)行驗證,只不過該階段的驗證著重于字節(jié)碼文件格式本身吕嘀,與“鏈接”階段的驗證側(cè)重點不同违寞。在鏈接階段贞瞒,著重于由字節(jié)碼信息出發(fā)進(jìn)行反向驗證,例如趁曼,驗證根據(jù)字節(jié)碼文件中的類名是否能夠找到對應(yīng)的類模板军浆。這些都驗證無誤之后,JVM才能放心地加載當(dāng)前類挡闰,也才能放心地將字節(jié)碼指令中對常量池索引號的引用重寫為直接引用乒融。

在正式使用Java類之前的最后一道工序便是“初始化”,這里所謂的初始化摄悯,并非指對類進(jìn)行實例化赞季,而是指執(zhí)行類的()方法。Java類的實例化奢驯,對應(yīng)的乃是 Java 類 生命周期中的”使用”段申钩。總體而言瘪阁,當(dāng)Java類中包含 static 修飾的靜態(tài)字段 撒遣,或者有使用 static{}塊包裹的代碼段時,編譯后便會在字節(jié)碼文件中包含一個名為()的方法管跺,JVM在初始化階段便會調(diào)用該方法义黎。需要說明一點,該方法僅能由Java編譯器生成并由JVM調(diào)用豁跑,程序開發(fā)者無法自定義一個同名的方法廉涕,更無法直接在Java程序中調(diào)用該方法 雖,該方法也是由字節(jié)碼指令所組成艇拍。

等JVM完成類的初始化之后狐蜕,便“萬事俱備,只欠東風(fēng)”卸夕,就等著開發(fā)者使用了馏鹤。使用方式多種多樣,其中最常見的一種方式是通過new關(guān)鍵字來實例化一個 Java 類娇哆。

當(dāng)然湃累,除了通過new關(guān)鍵字使用java類,還有多種方式碍讨,例如下面的例子:

該示例使用Class.forName(String)接口加載一個類治力,并通過Class.newlnstance()接口實例化一個類。

從廣義上說勃黍,類的加載也可以對應(yīng)類生命周期的7個階段中的前 5個階段即加載宵统、驗證 、 準(zhǔn)備 、解析和初始化马澈。當(dāng)類加載之后瓢省,JVM內(nèi)部會為Java類創(chuàng)建一個對等的類模板,類模板在JDK 6時代被存儲在所謂的perm區(qū)痊班,而到了JDK 8時代勤婚,則被存儲在所謂的metaSpace 區(qū)。無論存儲在哪里涤伐,當(dāng)存儲區(qū)即將被打爆而這個類又不再使用時馒胆,JVM的GC便有可能將其回收萌朱,即釋放內(nèi)存偏陪。而當(dāng)實例化一個 Java 類之后 ,JVM內(nèi)部則會為Java類實例對象創(chuàng)建一個對等的實例對象缺狠,該實例對象所存儲的區(qū)域與具體的 GC 策略緊密關(guān)聯(lián)器净,有可能在新生代型雳,也可能在老年代,當(dāng)然山害,更可能在棧上(棧上分配)四啰。當(dāng)類被使用完畢之后,JVM必須銷毀實例對象粗恢,否則只怕內(nèi)存區(qū)早晚會被打爆。JVM對類模板的銷毀和類實例對象的銷毀欧瘪,都是卸載眷射。

總體而言,Java類的生命周期如圖所示佛掖。

類加載

前文已經(jīng)描述過JVM對字節(jié)碼文件的精華部分的解析過程妖碉,當(dāng)字節(jié)碼文件解析完成之后,JVM便會在內(nèi)部創(chuàng)建一個與Java類對等的類模板對象芥被,說白了該對象其實是 C++類的實例欧宜。每一個Java類模型,最終在只叫內(nèi)部都會有一個 klassOop 與之對等 拴魄,Java 類中的字段 冗茸、方法及 常量池等都會保存到 klassOop 實例對象中。要注意匹中,這個實例對象并非 Java 類的實例對象夏漱,其僅僅用于表示 Java 類型本身,或者 Java 類的定義顶捷。與 Java 類實例對象對等的JVM內(nèi)部對象是instanceOop實例挂绰。

下面就從Java類模板對象instanceKlass 的創(chuàng)建開始講起。

前面描述過Java字節(jié)碼文件的常量池解析 服赎、字段解析與方法解析 葵蒂,這三部分內(nèi)容的解析便是 Java 字節(jié)碼文件的精華所在交播。當(dāng)這 3 個過程執(zhí)行完成之后,Java字節(jié)碼文件的精華便被分析完了践付,至此JVM便對Java類中所定義的一切數(shù)據(jù)結(jié)構(gòu)和算法“了如指掌”秦士,為了鞏固“勝利成果”,JVM需要將這些好不容易辛辛苦苦解析出來的結(jié)果保存起來荔仁。這些解析的結(jié)果會存儲到klassOop這個內(nèi)部類對象實例中伍宦,可以將該對象看作 Java 類在JVM內(nèi)部完全對等的一個鏡像,只不過Java類是寫給人類看的乏梁,而內(nèi)部鏡像 klassOop 則是寫給機(jī)器讀的次洼。當(dāng)成功保存解析結(jié)果之后,則 Java 類的生命周期的第一個階段加載遇骑,便大功告成卖毁。不看過程看結(jié)果,類加載階段其實就是為了這一目標(biāo)而來落萎,在JVM內(nèi)部創(chuàng)建一個與Java類結(jié)構(gòu)對等的數(shù)據(jù)對象亥啦。

從instanceKlass的結(jié)構(gòu)可以看到,其內(nèi)部定義了若干字段练链,這些字段足以存儲 Java 類規(guī)范所支持的一切信息翔脱,例如字段 、方法 媒鼓、內(nèi)部類等届吁,因為 instanceKlass 要作為 Java 類在JVM內(nèi)部對等的結(jié)構(gòu)體,所以能夠兼容Java類中的所有元素是其唯一的設(shè)計目標(biāo)绿鸣。但是 JVM在創(chuàng)建instanceKlass對象時疚沐,為其所申請的內(nèi)存空間卻超過了instanceKlass 類型本身所需的內(nèi)存大小,這是因為JVM需要在instanceKlass內(nèi)存空間的末尾再預(yù)留出足夠的空間潮模,存儲虛方法表 vtable接口表 itable 及 JAVA 類中的引用類型表 oopMap亮蛔。存儲虛方法表 vtable,其作用在前文分析Java

方法的解析機(jī)制時詳細(xì)描述過擎厢,這里不再贅述究流。itable與 oopMap 也是各有其作用。不過靜態(tài)字段在不同的 JDK 版本中存放的位置不同动遭,在JDK 6中梯嗽,靜態(tài)字段會被分配到 instanceKlass 實例對象所申請的內(nèi)存空間中,而在 JDK7和JDK8中沽损,靜態(tài)字段將會被分配到與 instanceKlass對等的鏡像類一 java .lang.Class 實例中灯节,關(guān)于靜態(tài)字段的分配及鏡像類會在下文詳細(xì)分析,此處先略過不表。

圖中的這個內(nèi)存數(shù)據(jù)結(jié)構(gòu)炎疆,便是Java類加載的最終產(chǎn)物卡骂,也是Java類在內(nèi)存中的對等體。JVM根據(jù)這個數(shù)據(jù)結(jié)構(gòu)形入,能夠獲取Java類中所定義的一切元素全跨。

類加載的最終結(jié)果便是在JVM的方法區(qū)創(chuàng)建一個與Java類對等的instanceKlass 實例對象,但是在JVM創(chuàng)建完instanceKlass之后亿遂,又創(chuàng)建了與之對等的另一個鏡像類java.lang.Class浓若。

JVM之所以在instanceKlass之外再創(chuàng)建一個mirror,是有用意的蛇数,總體而言挪钓,java.lang. Class是為了被 Java 程序調(diào)用,而 instanceKlass 則是為了被JVM內(nèi)部訪問耳舅。所以碌上,JVM直接暴露給Java的是 java_mirror,而不是 InstanceKlass 浦徊。

JDK8之所以將靜態(tài)字段從instanceKlass 遷移到mirror中馏予,也不是沒有道理。畢竟靜態(tài)字段并非Java類的成員變量盔性,如果從數(shù)據(jù)結(jié)構(gòu)這個角度看霞丧,靜態(tài)字段不能算作 Java 類這個數(shù)據(jù)結(jié)構(gòu)的一部分,因此 JDK 8 將靜態(tài)字段轉(zhuǎn)移到 mirror 中冕香。從反射的角度看蛹尝,靜態(tài)字段放在 mirror 中是合理的,畢竟在進(jìn)行反射時暂筝,需要給 出 Java 類中所定義的全部字段,無論字段是不是靜態(tài)類型硬贯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末焕襟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子饭豹,更是在濱河造成了極大的恐慌鸵赖,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拄衰,死亡現(xiàn)場離奇詭異它褪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)翘悉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門茫打,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事老赤÷盅螅” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵抬旺,是天一觀的道長弊予。 經(jīng)常有香客問我,道長开财,這世上最難降的妖魔是什么汉柒? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮责鳍,結(jié)果婚禮上碾褂,老公的妹妹穿的比我還像新娘。我一直安慰自己薇搁,他們只是感情好斋扰,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啃洋,像睡著了一般传货。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宏娄,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天问裕,我揣著相機(jī)與錄音,去河邊找鬼孵坚。 笑死粮宛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的卖宠。 我是一名探鬼主播巍杈,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扛伍!你這毒婦竟也來了筷畦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤刺洒,失蹤者是張志新(化名)和其女友劉穎鳖宾,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逆航,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡鼎文,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了因俐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拇惋。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡周偎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蚤假,到底是詐尸還是另有隱情栏饮,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布磷仰,位于F島的核電站袍嬉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏灶平。R本人自食惡果不足惜伺通,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逢享。 院中可真熱鬧罐监,春花似錦、人聲如沸瞒爬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侧但。三九已至矢空,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間禀横,已是汗流浹背屁药。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留柏锄,地道東北人酿箭。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像趾娃,于是被迫代替她去往敵國和親缭嫡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344