JVM類加載機(jī)制

1.前言

前面的幾個(gè)章節(jié)了解了JVM的基礎(chǔ)知識(shí),直到了JVM的底層結(jié)構(gòu)及內(nèi)存的回收策略,這章接著學(xué)習(xí)JVM加載類的過程

2.目錄

目錄

3.類的加載過程

虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn),轉(zhuǎn)化解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型

類加載過程
  • 整個(gè)生命周期包括:加載(Loading),驗(yàn)證(Verification),準(zhǔn)備(Preparation),解析(Preparation),初始化(Initialization),使用(Using)和卸載(Unloading)7個(gè)階段.其中準(zhǔn)備,驗(yàn)證,解析3個(gè)部分統(tǒng)稱為連接(Linking)
  • 加載,驗(yàn)證,準(zhǔn)備,初始化和卸載的執(zhí)行順序是確定的解析階段則在某些情況下可以在初始化階段之后再開始,這就是Java語言的運(yùn)行時(shí)綁定(也稱動(dòng)態(tài)綁定或晚期綁定)

3.1.加載階段

加載階段有以下三個(gè)步驟:

  • 通過類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流
  • 將這個(gè)字節(jié)流所代表的的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
  • 在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口

其中獲取二進(jìn)制字節(jié)流可以通過Class文件,ZIP包,網(wǎng)絡(luò),運(yùn)行時(shí)(動(dòng)態(tài)代理),JSP生成,數(shù)據(jù)庫等途徑獲取

需要注意的是數(shù)組類的加載,數(shù)組類并不通過類加載器加載,而是由Java虛擬機(jī)直接創(chuàng)建,但是數(shù)組類還是要依靠類加載器進(jìn)行加載

這些二進(jìn)制字節(jié)流加載完成之后,按照指定的格式存放于方法區(qū)內(nèi)(Java7及以前方法區(qū)位于永久代,Java8位于元空間).然后再方法區(qū)生成一個(gè)比較特殊的java.lang.Class對(duì)象,用來作為程序訪問方法區(qū)中這些類型數(shù)據(jù)的外部接口

3.2.驗(yàn)證階段

驗(yàn)證的目的是確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全

驗(yàn)證階段
  • 文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范;比如,是否以魔術(shù)0XCAFEBABE開頭,主次版本號(hào)是否在當(dāng)前虛擬機(jī)的處理范圍之內(nèi),常量池中的常量是否有不被支持的類型.只有驗(yàn)證通過才會(huì)進(jìn)入方法區(qū)進(jìn)行存儲(chǔ)
  • 元數(shù)據(jù)驗(yàn)證:對(duì)自己餓嗎描述的信息進(jìn)行語義分析,以保證其描述的信息符合Java語言規(guī)范的要求;比如,是否有父類(除Object類),父類是否為final修飾,是否實(shí)現(xiàn)抽象方法或接口,重載是否正確等
  • 字節(jié)碼驗(yàn)證:通過字節(jié)流和控制流分析,確定程序語義是合法的,符合邏輯的.比如,保證數(shù)據(jù)類型與指令正常配合工作,指令不會(huì)跳轉(zhuǎn)到方法體外的字節(jié)碼上,方法體中的類型轉(zhuǎn)換是有效的等
  • 符號(hào)引用驗(yàn)證:在虛擬機(jī)將符號(hào)引用轉(zhuǎn)化為直接引用的時(shí)候進(jìn)行驗(yàn)證,可以看做是對(duì)類自身以外的信息(常量池中的各種符號(hào)引用)進(jìn)行匹配性的校驗(yàn).常見的異常比如:java.lang.NoSuchMethodError,java.lang.NoSuchFiledError

3.3.準(zhǔn)備階段

主被截?cái)嘀饕钦菫轭愖兞糠峙鋬?nèi)存并設(shè)置類變量的初始值,變量所使用的內(nèi)存豆?jié){在方法區(qū)中進(jìn)行分配

此處的類變量指的是被static修飾的變量,不包含實(shí)例變量,實(shí)例變量在對(duì)象實(shí)例化階段分配在堆中

public static String a = "A";

并且,變量的初始化值并不是類中定義的值,而是該變量所屬類型的默認(rèn)值

準(zhǔn)備階段

當(dāng)然,也有特殊情況,比如當(dāng)變量被final修飾時(shí):此時(shí),該字段屬性是ConstantValue時(shí),會(huì)在主被截?cái)喑跏蓟癁橹付ǖ闹?/p>

3.4.解析階段

解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程.解析動(dòng)作主要針對(duì)類或接口,字段,類方法,接口方法,方法類型,方法句柄和調(diào)用點(diǎn)限定符7類符號(hào)引用進(jìn)行

這里我們看一下字段解析,也就是最開始第一道面試題.當(dāng)獲取SubClass的屬性a時(shí),首先會(huì)查找SubClass本身是否包含該字段,如果包含則直接返回引用,查找結(jié)束

否則,如果SubClass類實(shí)現(xiàn)了接口或繼承了父類,那么則遞歸搜索各個(gè)接口和父類,找到匹配的屬性則返回,查找結(jié)束

否則,查找失敗,拋出java.lang.NoSuchFieldError異常.如果返回成功了,但是是權(quán)限校驗(yàn)失敗,也就是無該字段的訪問權(quán)限,則拋出java.lang.illegalAccessError異常

其它形式的解析,就不在這里一一說明了

3.5.初始化階段

初始化階段才是真正執(zhí)行類中定義的Java程序代碼(字節(jié)碼).在此階段會(huì)根據(jù)代碼進(jìn)行類變量和其他資源的初始化,或者可以從另一個(gè)角度來表達(dá):初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程

<clinit>()方法是由編譯器自動(dòng)收集類中的所有變量的復(fù)制動(dòng)作和靜態(tài)語句塊(static語句塊)中的語句合并生成的,編譯器收集的順序是由語句在源文件中出現(xiàn)的順序決定的,靜態(tài)語句塊中只能訪問到定義在靜態(tài)語句塊之前的變量,定義在它之后的變量,在前面的靜態(tài)語句塊中可以賦值,但是不能訪問

屬性訪問測(cè)試

注釋掉錯(cuò)誤提示行,打印結(jié)果為A,在準(zhǔn)備階段屬性a的值為null,然后類初始化按照順序執(zhí)行,首先執(zhí)行static塊中的a="B",接著執(zhí)行a="A"的復(fù)制操作,此時(shí)值為A,當(dāng)main方法調(diào)用打印時(shí)則為A

<clinit>()方法與實(shí)例構(gòu)造器<init>()方法不同,它不需要顯示地調(diào)用父類構(gòu)造器,虛擬機(jī)會(huì)保證在子類<clinit>()方法執(zhí)行之前,父類的<clinit>()方法已經(jīng)執(zhí)行完畢

由于父類的<clinit>()方法先執(zhí)行,也就意味著父類中定義的靜態(tài)語句塊要優(yōu)先于子類的變量賦值操作

<clinit>()方法對(duì)于類或者接口來說并不是行必須的,如果一個(gè)類中沒有靜態(tài)語句塊,也沒有對(duì)變量賦值操作,那么編譯器可以不為這個(gè)類生產(chǎn)<clinit>()方法

接口中不能使用靜態(tài)語句塊,但仍然有變量初始化的復(fù)制操作,因此接口與類一樣都會(huì)生成<clinit>()方法.但接口與類不同的是,執(zhí)行接口的<clinit>()方法不需要先執(zhí)行父接口的<clinit>()方法.只有當(dāng)父接口中定義的變量使用時(shí),父接口才會(huì)初始化.另外,接口的實(shí)現(xiàn)類在初始化時(shí)也一樣不會(huì)執(zhí)行接口的<clinit>()方法

虛擬機(jī)會(huì)保證一個(gè)類的<clinit>()方法在多線程環(huán)境中被正確的加鎖,同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的<clinit>()方法,其它線程都需要等待,直到活動(dòng)線程執(zhí)行<clinit>()方法執(zhí)行完畢.如果在一個(gè)類的<clinit>()方法中有耗時(shí)很長(zhǎng)的操作,就可能造成逗哥線程阻塞,在實(shí)際應(yīng)用中這種阻塞旺旺是隱藏的

  • <clinit>:在jvm第一次加載class文件時(shí)調(diào)用,包括靜態(tài)變量初始化語句和靜態(tài)塊的執(zhí)行
  • <init>:在實(shí)例創(chuàng)建出來的時(shí)候調(diào)用,包括調(diào)用new操作符,調(diào)用Class或java.lang.reflect.Constructor對(duì)象的newInstance()方法,調(diào)用任何有對(duì)象的clone()方法,通過java.io.ObjectInputStream類的getObject()方法反序列化

3.6.虛擬機(jī)規(guī)范初始化

虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了有且只有5中情況(jdk1.7)必須對(duì)類進(jìn)行"初始化"(而加載、驗(yàn)證、準(zhǔn)備自然需要在此之前開始):

  • 遇到new,getStatic,putStatic,invokeStatic這失調(diào)字節(jié)碼指令時(shí),如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化.生成這4條指令的最常見的Java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候,讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾,已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候
  • 使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化啥容。
  • 當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化
  • 當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類
  • 當(dāng)使用jdk1.7動(dòng)態(tài)語言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒有進(jìn)行初始化,則需要先出觸發(fā)其初始化

4.小結(jié)

這章主要講解了類的加載過程,對(duì)底層數(shù)據(jù)的存儲(chǔ),分布和加載有了大致的了解,知識(shí)點(diǎn)比較多,有一個(gè)整體的認(rèn)識(shí)就可以,下一個(gè)講解類加載器及雙親委派機(jī)制


原文:https://www.choupangxia.com/2019/10/27/interview-jvm-load-01/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市已亥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌来屠,老刑警劉巖虑椎,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異俱笛,居然都是意外死亡捆姜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門嫂粟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來娇未,“玉大人,你說我怎么就攤上這事星虹×闾В” “怎么了镊讼?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)平夜。 經(jīng)常有香客問我蝶棋,道長(zhǎng),這世上最難降的妖魔是什么忽妒? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任玩裙,我火速辦了婚禮,結(jié)果婚禮上段直,老公的妹妹穿的比我還像新娘吃溅。我一直安慰自己,他們只是感情好鸯檬,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布决侈。 她就那樣靜靜地躺著,像睡著了一般喧务。 火紅的嫁衣襯著肌膚如雪赖歌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天功茴,我揣著相機(jī)與錄音庐冯,去河邊找鬼。 笑死坎穿,一個(gè)胖子當(dāng)著我的面吹牛展父,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赁酝,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼犯祠,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼旭等!你這毒婦竟也來了酌呆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤搔耕,失蹤者是張志新(化名)和其女友劉穎隙袁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弃榨,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡菩收,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鲸睛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娜饵。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖官辈,靈堂內(nèi)的尸體忽然破棺而出箱舞,到底是詐尸還是另有隱情遍坟,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布晴股,位于F島的核電站愿伴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏电湘。R本人自食惡果不足惜隔节,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寂呛。 院中可真熱鬧怎诫,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽普监。三九已至,卻和暖如春肤寝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工阀圾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狗唉。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓初烘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親分俯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肾筐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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