JVM:类加载机制之类加载过程
?
類加載機(jī)制概念
?Java虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)、準(zhǔn)備、解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型,這就是虛擬機(jī)的加載機(jī)制。*
Class文件由類裝載器裝載后,在JVM中將形成一份描述Class結(jié)構(gòu)的元信息對象,通過該元信息對象可以獲知Class的結(jié)構(gòu)信息:如構(gòu)造函數(shù),屬性和方法等,Java允許用戶借由這個(gè)Class相關(guān)的元信息對象間接調(diào)用Class對象的功能,這里就是我們經(jīng)常能見到的Class類。
類從被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止,它的整個(gè)生命周期包括了:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(using)、和卸載(Unloading)七個(gè)階段。其中驗(yàn)證、準(zhǔn)備和解析三個(gè)部分統(tǒng)稱為連接(Linking),這七個(gè)階段的發(fā)生順序如下圖所示:?
工作機(jī)制
類裝載器就是尋找類的字節(jié)碼文件,并構(gòu)造出類在JVM內(nèi)部表示的對象組件。在Java中,類裝載器把一個(gè)類裝入JVM中,要經(jīng)過以下步驟:
?(1) 裝載:查找和導(dǎo)入Class文件; (2) 鏈接:把類的二進(jìn)制數(shù)據(jù)合并到JRE中;(a)校驗(yàn):檢查載入Class文件數(shù)據(jù)的正確性;(b)準(zhǔn)備:給類的靜態(tài)變量分配存儲空間;(c)解析:將符號引用轉(zhuǎn)成直接引用;(3) 初始化:對類的靜態(tài)變量,靜態(tài)代碼塊執(zhí)行初始化操作1. 裝載(加載) (重點(diǎn))
什么是類的裝載
類的裝載指的是將類的.class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個(gè)java.lang.Class對象,用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。類的加載的最終產(chǎn)品是位于堆區(qū)中的Class對象,Class對象封裝了類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu),并且向Java程序員提供了訪問方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口。?
?
類加載器并不需要等到某個(gè)類被“首次主動使用”時(shí)再加載它,JVM規(guī)范允許類加載器在預(yù)料某個(gè)類將要被使用時(shí)就預(yù)先加載它,如果在預(yù)先加載的過程中遇到了.class文件缺失或存在錯(cuò)誤,類加載器必須在程序首次主動使用該類時(shí)才報(bào)告錯(cuò)誤(LinkageError錯(cuò)誤)如果這個(gè)類一直沒有被程序主動使用,那么類加載器就不會報(bào)告錯(cuò)誤。
加載.class文件的方式有:
1.從本地系統(tǒng)中直接加載
2.通過網(wǎng)絡(luò)下載.class文件
3.從zip,jar等歸檔文件中加載.class文件
4.從專有數(shù)據(jù)庫中提取.class文件
5. 將Java源文件動態(tài)編譯為.class文件
在了解了什么是類的加載后,回頭來再看jvm進(jìn)行類加載階段都做了什么。虛擬機(jī)需要完成以下三件事情:
1.通過一個(gè)類的全限定名稱來獲取定義此類的二進(jìn)制字節(jié)流。 2.將這個(gè)字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。 3.在java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對象,作為方法區(qū)這些數(shù)據(jù)的訪問入口。相對于類加載過程的其他階段,加載階段是開發(fā)期相對來說可控性比較強(qiáng),該階段既可以使用系統(tǒng)提供的類加載器完成,也可以由用戶自定義的類加載器來完成,開發(fā)人員可以通過定義自己的類加載器去控制字節(jié)流的獲取方式。關(guān)于這個(gè)過程的更多細(xì)節(jié),我會在下一節(jié)細(xì)說,類的加載。?
加載階段完成后,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲在方法區(qū)之中,而且在Java堆中也創(chuàng)建一個(gè)java.lang.Class類的對象,這樣便可以通過該對象訪問方法區(qū)中的這些數(shù)據(jù)。
2. 驗(yàn)證
驗(yàn)證的目的是為了確保Class文件中的字節(jié)流包含的信息符合當(dāng)前虛擬機(jī)的要求,而且不會危害虛擬機(jī)自身的安全。不同的虛擬機(jī)對類驗(yàn)證的實(shí)現(xiàn)可能會有所不同,但大致都會完成以下四個(gè)階段的驗(yàn)證:文件格式的驗(yàn)證、元數(shù)據(jù)的驗(yàn)證、字節(jié)碼驗(yàn)證和符號引用驗(yàn)證。
?1)文件格式的驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理,該驗(yàn)證的主要目的是保證輸入的字節(jié)流能正確地解析并存儲于方法區(qū)之內(nèi)。經(jīng)過該階段的驗(yàn)證后,字節(jié)流才會進(jìn)入內(nèi)存的方法區(qū)中進(jìn)行存儲,后面的三個(gè)驗(yàn)證都是基于方法區(qū)的存儲結(jié)構(gòu)進(jìn)行的。 ?2)元數(shù)據(jù)驗(yàn)證:對類的元數(shù)據(jù)信息進(jìn)行語義校驗(yàn)(其實(shí)就是對類中的各數(shù)據(jù)類型進(jìn)行語法校驗(yàn)),保證不存在不符合Java語法規(guī)范的元數(shù)據(jù)信息。 ?3)字節(jié)碼驗(yàn)證:該階段驗(yàn)證的主要工作是進(jìn)行數(shù)據(jù)流和控制流分析,對類的方法體進(jìn)行校驗(yàn)分析,以保證被校驗(yàn)的類的方法在運(yùn)行時(shí)不會做出危害虛擬機(jī)安全的行為。 4 )符號引用驗(yàn)證:這是最后一個(gè)階段的驗(yàn)證,它發(fā)生在虛擬機(jī)將符號引用轉(zhuǎn)化為直接引用的時(shí)候(解析階段中發(fā)生該轉(zhuǎn)化,后面會有講解),主要是對類自身以外的信息(常量池中的各種符號引用)進(jìn)行匹配性的校驗(yàn)。學(xué)習(xí)鏈接的驗(yàn)證這一步,需要了解class文件的結(jié)構(gòu)!
其實(shí)文件格式的驗(yàn)證、元數(shù)據(jù)的驗(yàn)證、字節(jié)碼的驗(yàn)證、符號引用的驗(yàn)證,其實(shí)都是保證class文件格式的正確性!
? ? ? ? ? ? ? ? ? ? ?
3. 準(zhǔn)備
準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配。?
注:(方法區(qū))
1)這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(靜態(tài)變量static),而不包括實(shí)例變量,實(shí)例變量會在對象實(shí)例化時(shí)隨著對象一塊分配在Java堆中,例如上圖,這一階段,v只會被初始化為0,而不會賦值給1!賦值給1是在初始化階段才會按照用戶程序的設(shè)定值進(jìn)行初始化!但是,對于static final 變量會直接設(shè)值初始化!
2)這里所設(shè)置的初始值通常情況下是數(shù)據(jù)類型默認(rèn)的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。
4. 解析
解析階段是虛擬機(jī)將常量池內(nèi)的符號引用替換為直接引用的過程。
比如說你的父類是com.imooc.src.Pojo,那么這就是符號引用,說白了就是一個(gè)字符串!但是我們在運(yùn)行的時(shí)候需要的是內(nèi)存的地址!因此此階段就是將這些字符串引用轉(zhuǎn)化為直接引用(也就是這個(gè)字符串引用在實(shí)際內(nèi)存中的地址)
符號引用(Symbolic Reference):符號引用以一組符號來描述所引用的目標(biāo),符號引用可以是任何形式的字面量,符號引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān),引用的目標(biāo)并不一定已經(jīng)在內(nèi)存中。 直接引用(Direct Reference):直接引用可以是直接指向目標(biāo)的指針、相對偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。直接引用是與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān)的,同一個(gè)符號引用在不同的虛擬機(jī)實(shí)例上翻譯出來的直接引用一般都不相同,如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。 1、類或接口的解析:判斷所要轉(zhuǎn)化成的直接引用是對數(shù)組類型,還是普通的對象類型的引用,從而進(jìn)行不同的解析。 2、字段解析:對字段進(jìn)行解析時(shí),會先在本類中查找是否包含有簡單名稱和字段描述符都與目標(biāo)相匹配的字段,如果有,則查找結(jié)束;如果沒有,則會按照繼承關(guān)系從上往下遞歸搜索該類所實(shí)現(xiàn)的各個(gè)接口和它們的父接口,還沒有,則按照繼承關(guān)系從上往下遞歸搜索其父類,直至查找結(jié)束。 3、類方法解析:對類方法的解析與對字段解析的搜索步驟差不多,只是多了判斷該方法所處的是類還是接口的步驟,而且對類方法的匹配搜索,是先搜索父類,再搜索接口。 4、接口方法解析:與類方法解析步驟類似,只是接口不會有父類,因此,只遞歸向上搜索父接口就行了。5. 初始化
類初始化階段是類加載過程的最后一步,前面的類加載過程中,除了加載(Loading)階段用戶應(yīng)用程序可以通過自定義類加載器參與之外,其余動作完全由虛擬機(jī)主導(dǎo)和控制。到了初始化階段,才真正開始執(zhí)行類中定義的Java程序代碼。?
初始化,為類的static靜態(tài)變量賦予正確的初始值,JVM負(fù)責(zé)對類進(jìn)行初始化,主要對類變量進(jìn)行初始化(靜態(tài)變量static)。在Java中對類變量進(jìn)行初始值設(shè)定有兩種方式:
JVM初始化步驟
1、假如這個(gè)類還沒有被加載和連接,則程序先加載并連接該類 2、假如該類的直接父類還沒有被初始化,則先初始化其直接父類 3、假如類中有初始化語句,則系統(tǒng)依次執(zhí)行這些初始化語句初始化階段是執(zhí)行類構(gòu)造器()方法的過程。
類初始化的觸發(fā)條件:只有當(dāng)對類的主動使用的時(shí)候才會導(dǎo)致類的初始化。
?(1) 創(chuàng)建類的實(shí)例,也就是new的方式
(2) 訪問某個(gè)類或接口的靜態(tài)變量,或者對該靜態(tài)變量賦值
(3) 調(diào)用類的靜態(tài)方法
(4) 反射(如Class.forName(“com.shengsiyuan.Test”))
(5) 初始化某個(gè)類的子類,則其父類也會被初始化
(6) Java虛擬機(jī)啟動時(shí)被標(biāo)明為啟動類的類(Java Test),直接使用java.exe命令來運(yùn)行某個(gè)主類
除此以外,所有其他方式都不會觸發(fā)初始化,稱為被動引用。?
?
結(jié)束生命周期
在以下情況的時(shí)候,Java虛擬機(jī)會結(jié)束生命周期?
1. 執(zhí)行了System.exit()方法?
2. 程序正常執(zhí)行結(jié)束?
3. 程序在執(zhí)行過程中遇到了異?;蝈e(cuò)誤而異常終止?
4. 由于操作系統(tǒng)出現(xiàn)錯(cuò)誤而導(dǎo)致Java虛擬機(jī)進(jìn)程終止
總結(jié)
以上是生活随笔為你收集整理的JVM:类加载机制之类加载过程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM:类加载机制之类加载器
- 下一篇: 获取application.yml中的属