新聞中心
本篇內(nèi)容主要講解“JVM加載機(jī)制是什么”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“JVM加載機(jī)制是什么”吧!
十余年的普洱網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。營(yíng)銷型網(wǎng)站建設(shè)的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整普洱建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無(wú)論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)建站從事“普洱網(wǎng)站設(shè)計(jì)”,“普洱網(wǎng)站推廣”以來(lái),每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
類加載
Java虛擬機(jī)類加載過(guò)程是把Class類文件加載到內(nèi)存,并對(duì)Class文件中的數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的java類型的過(guò)程
和那些編譯時(shí)需要連接工作的語(yǔ)言不同,在Java語(yǔ)言里,類型的加載,連接和初始化過(guò)程都是在程序 運(yùn)行期間完成的,這種策略雖然會(huì)令類加載時(shí)稍微增加一些性能開銷,但是會(huì)為java應(yīng)用程序提供比較高的靈活性。
當(dāng)我們使用到某個(gè)類的時(shí)候,如果這個(gè)類還未從磁盤上加載到內(nèi)存中,JVM就會(huì)通過(guò)三步走策略(加載、連接、初始化)來(lái)對(duì)這個(gè)類進(jìn)行初始化,JVM完成這三個(gè)步驟的名稱,就叫做類加載或者類初始化
類加載的時(shí)機(jī)
什么情況下需要開始類加載的第一個(gè)階段——加載 ,在Java虛擬機(jī)規(guī)范中沒(méi)有進(jìn)行強(qiáng)制約束,而是交給虛擬機(jī)的具體實(shí)現(xiàn)來(lái)進(jìn)行把握,但是對(duì)于初始化階段,虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了 “有且只有” 五種情況必須立即對(duì)類進(jìn)行初始化(而加載、驗(yàn)證、準(zhǔn)備自然需要在此之前開始),具體情況如下所示:
class文件的加載時(shí)機(jī):
關(guān)于序號(hào)1的詳細(xì)解釋:
使用 new 關(guān)鍵字實(shí)例化對(duì)象時(shí)
讀取類的靜態(tài)變量時(shí)(被 final修飾,已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)
設(shè)置類的靜態(tài)變量時(shí)
調(diào)用一個(gè)類的靜態(tài)方法時(shí)
注意:newarray指令觸發(fā)的只是數(shù)組類型本身的初始化,而不會(huì)導(dǎo)致其相關(guān)類型的初始化,比如, newString[]只會(huì)直接觸發(fā) String[]類的初始化,也就是觸發(fā)對(duì)類 [Ljava.lang.String的初始化,而直接不會(huì)觸發(fā) String類的初始化。
生成這四條指令最常見的Java代碼場(chǎng)景是:
對(duì)于這5種會(huì)觸發(fā)類進(jìn)行初始化的場(chǎng)景,虛擬機(jī)規(guī)范中使用了一個(gè)很強(qiáng)烈的限定語(yǔ):“有且只有”,這5種場(chǎng)景中的行為稱為對(duì)一個(gè)類進(jìn)行主動(dòng)引用。除此之外,所有引用類的方式都不會(huì)觸發(fā)初始化,稱為 被動(dòng)引用。
需要特別指出的是,類的實(shí)例化和類的初始化是兩個(gè)完全不同的概念:
類的實(shí)例化是指創(chuàng)建一個(gè)類的實(shí)例(對(duì)象)的過(guò)程;
類的初始化是指為類各個(gè)成員賦初始值的過(guò)程,是類生命周期中的一個(gè)階段;
被動(dòng)引用的三個(gè)場(chǎng)景:
通過(guò)子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化
/** * @program: jvm * @ClassName Test1 * @Description:通過(guò)子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化 * @author: 牧小農(nóng) * @create: 2021-02-27 11:42 * @Version 1.0 **/ public class Test1 { static { System.out.println("Init Superclass!!!"); } public static void main(String[] args) { int x = Son.count; } } class Father extends Test1{ static int count = 1; static { System.out.println("Init father!!!"); } } class Son extends Father{ static { System.out.println("Init son!!!"); } }
輸出:
Init Superclass!!! Init father!!!
對(duì)于靜態(tài)字段,只有直接定義這個(gè)字段的類才會(huì)被初始化,因此通過(guò)其子類來(lái)引用父類中定義的靜態(tài)字段,只會(huì)觸發(fā)父類的初始化而不會(huì)觸發(fā)子類的初始化。至于是否要觸發(fā)子類的加載和驗(yàn)證,在虛擬機(jī)中并未明確規(guī)定,這點(diǎn)取決于虛擬機(jī)的具體實(shí)現(xiàn)。對(duì)于Sun HotSpot虛擬機(jī)來(lái)說(shuō),可通過(guò)-XX:+TraceClassLoading參數(shù)觀察到此操作會(huì)導(dǎo)致子類的加載。
上面的案例中,由于count字段是在Father類中定義的,因此該類會(huì)被初始化,此外,在初始化類Father的時(shí)候,虛擬機(jī)發(fā)現(xiàn)其父類Test1 還沒(méi)被初始化,因此虛擬機(jī)將先初始化其父類Test1 ,然后初始化子類Father,而Son始終不會(huì)被初始化;
通過(guò)數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)此類的初始化
/** * @program: jvm * @ClassName Test2 * @description: * @author: muxiaonong * @create: 2021-02-27 12:03 * @Version 1.0 **/ public class Test2 { public static void main(String[] args) { M[] m = new M[8]; } } class M{ static { System.out.println("Init M!!!"); } }
運(yùn)行之后我們會(huì)發(fā)現(xiàn)沒(méi)有輸出 "Init M!!!",說(shuō)明沒(méi)有觸發(fā)類的初始化階段
常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒(méi)有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化
/** * @program: jvm * @ClassName Test3 * @description: * @author: muxiaonong * @create: 2021-02-27 12:05 * @Version 1.0 **/ public class Test3 { public static void main(String[] args) { System.out.println(ConstClass.COUNT); } } class ConstClass{ static final int COUNT = 1; static{ System.out.println("Init ConstClass!!!"); } }
上面代碼運(yùn)行后也沒(méi)有輸出 InitConstClass!!!,這是因?yàn)殡m然在Java源碼中引用了ConstClass 類中的常量COUNT ,但其實(shí)在編譯階段通過(guò)常量傳播優(yōu)化,已經(jīng)將常量的值 "1"存儲(chǔ)到Test3 常量池中了,對(duì)常量ConstClass.COUNT的引用實(shí)際都被轉(zhuǎn)化為Test3 類對(duì)自身常量池的引用了,也就是說(shuō),實(shí)際上Test3 的Class文件之中并沒(méi)有ConstClass類的符號(hào)引用入口,這兩個(gè)類在編譯為Class文件之后就不存在關(guān)系
類加載過(guò)程
有一個(gè)名叫Class文件,它靜靜的躺在了硬盤上,吃香的喝辣的,他究竟需要一個(gè)怎么樣的過(guò)程經(jīng)歷了什么,才能夠從舒服的硬盤中到內(nèi)存中呢?class進(jìn)入內(nèi)存總共有三大步。
加載(Loading)
連接(Linking)
初始化(Initlalizing)
1、加載
加載是 類加載(Class Loading) 過(guò)程的一個(gè)階段,加載 是 類加載(Class Loading) 過(guò)程的一個(gè)階段,加載是指將當(dāng)前類的class文件讀入內(nèi)存中,并且創(chuàng)建一個(gè) java.lang.Class的對(duì)象,也就是說(shuō),當(dāng)程序中使用任何類的時(shí)候,系統(tǒng)都會(huì)創(chuàng)建一個(gè)叫 java.lang.Class對(duì)象
在加載階段,虛擬機(jī)需要完成以下三個(gè)事情:
通過(guò)一個(gè)類的全限定名類獲取定義此類的二進(jìn)制字節(jié)流(沒(méi)有指明只能從一個(gè)Class文件中獲取,可以從其他渠道,如:網(wǎng)絡(luò)、動(dòng)態(tài)生成、數(shù)據(jù)庫(kù)等)
將這個(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ù)的訪問(wèn)入口
類加載器通常無(wú)須等到“首次使用”該類時(shí)才加載該類,Java虛擬機(jī)規(guī)范允許系統(tǒng)預(yù)先加載某些類。加載階段與連接階段的部分內(nèi)容是交叉進(jìn)行的,加載階段尚未完成,連接階段可能已經(jīng)開始,但這些夾在夾在階段之中進(jìn)行的動(dòng)作,仍然屬于連接階段的內(nèi)容,這兩個(gè)階段的開始時(shí)間仍然保持著固定的先后順序。
2、連接
當(dāng)類被加載之后,系統(tǒng)會(huì)生成一個(gè)對(duì)應(yīng)的Class對(duì)象,就會(huì)進(jìn)入 連接階段,連接階段負(fù)責(zé)把類的二進(jìn)制數(shù)據(jù)合并到JRE中,連接階段又分為三個(gè)小階段
1.1 驗(yàn)證
驗(yàn)證是連接階段的第一步,這一階段的主要目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。Java語(yǔ)言相對(duì)于 C/C++ 來(lái)說(shuō)本身是相對(duì)安全的語(yǔ)言,驗(yàn)證階段是非常重要的,這個(gè)階段是否嚴(yán)謹(jǐn),決定了Java虛擬機(jī)能不能承受惡意代碼的攻擊,當(dāng)驗(yàn)證輸入的字節(jié)流不符合Class文件格式的約束時(shí),虛擬機(jī)會(huì)拋出一個(gè) java.lang.VerifyError異?;蛘咦宇惍惓?,從大體來(lái)說(shuō)驗(yàn)證主要分為四個(gè)校驗(yàn)動(dòng)作:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證、符號(hào)引用驗(yàn)證
文件格式驗(yàn)證:主要驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理。主要包含以下幾個(gè)方面:
文件格式是否以 CAFEBABE開頭
主次版本是否在虛擬機(jī)處理的范圍內(nèi)
常量池的常量是否有不被支持的常量類型
指向常量的各種索引值是否有指向不存在的常量或者不符合類型的常量
CONSTANTUtf8info 型的常量是否有不符合UTF8編碼的數(shù)據(jù)
Class文件中各個(gè)部分及文件本身是否有被刪除的活附件的信息
元數(shù)據(jù)驗(yàn)證: 主要是對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析,主要目的是對(duì)類的元數(shù)據(jù)進(jìn)行語(yǔ)義校驗(yàn),分析是否符合Java的 語(yǔ)言語(yǔ)法的規(guī)范,保證不存在不符合Java語(yǔ)言的規(guī)范的元數(shù)據(jù)的信息,該階段主要驗(yàn)證的方面包含以下幾個(gè)方面:
這個(gè)類是否有父類(除java.lang.Object)
這個(gè)類的父類是否繼承了不允許被繼承的類(被final 修飾的類)
如果這個(gè)類不是抽象類,是否實(shí)現(xiàn)了父類或接口之中要求的所有方法
類中的字段、方法是否和父類產(chǎn)生矛盾
字節(jié)碼驗(yàn)證: 最重要也是最復(fù)雜的校驗(yàn)環(huán)節(jié),通過(guò)數(shù)據(jù)流和控制流分析程序語(yǔ)義是否合法、符合邏輯的。主要針對(duì)類的方法體進(jìn)行校驗(yàn)分析,保證被校驗(yàn)的類在運(yùn)行時(shí)不會(huì)危害虛擬機(jī)安全的事情
保證任何時(shí)候操作數(shù)棧的數(shù)據(jù)類型和指令代碼序列都能配合工作(例如在操作棧上有一個(gè)int類型的數(shù)據(jù),保證不會(huì)在使用的時(shí)候按照l(shuí)ong類型來(lái)加載到本地變量表中)
跳轉(zhuǎn)指令不會(huì)條狀到方法體以外的字節(jié)碼指令上
保證方法體中的數(shù)據(jù)轉(zhuǎn)換是有效的,例如可以把一個(gè)子類對(duì)象賦值給父類數(shù)據(jù)類型,但是不能把父類賦值給子類數(shù)據(jù)類型
符號(hào)引用驗(yàn)證: 針對(duì)符號(hào)引用轉(zhuǎn)換直接引用的時(shí)候,這個(gè)裝換工作會(huì)在第三階段(字節(jié)碼驗(yàn)證)解析階段中發(fā)生。主要是保證引用一定會(huì)被訪問(wèn)到,不會(huì)出現(xiàn)類無(wú)法訪問(wèn)的問(wèn)題。
1.2 準(zhǔn)備
為類變量 分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都會(huì)在方法區(qū)進(jìn)行分配,在準(zhǔn)備階段是把class文件靜態(tài)變量賦默認(rèn)值,注意:不是賦初始值,比如我們 publicstaticinti=8,在這個(gè)步驟 并不是把 i 賦值成8 ,而是先賦值為0
基本類型的默認(rèn)值:
在通常情況下初始值是0,但是如果我們把上面的常量加一個(gè)final 類修飾的話,那么這個(gè)時(shí)候初始值就會(huì)編程我們指定的值 publicstaticfinalinti=8編譯的時(shí)候Javac會(huì)把i的初始值變?yōu)?,
1.3 解析
把class文件常量池里面用到的符號(hào)引用轉(zhuǎn)換為直接內(nèi)存地址,直接可以訪問(wèn)到的內(nèi)容符號(hào)引用:以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何字面形式的字面量,只要不會(huì)出現(xiàn)沖突能夠定位到就可以直接引用:可以是直接指向目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄,如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在了
3、初始化
初始化是給類的靜態(tài)變量賦正確的初始值,剛才我們有講到準(zhǔn)備階段是復(fù)制默認(rèn)值,而初始化是給靜態(tài)變量賦值初始值,看下面的語(yǔ)句:
public static int i = 8
首先字節(jié)碼文件被加載到內(nèi)存后,先進(jìn)行連接驗(yàn)證,通過(guò)準(zhǔn)備階段,給i分配內(nèi)存,因?yàn)槭莝tatic,所以這個(gè)時(shí)候i 等于int類型的默認(rèn)初始值是0,所以i 現(xiàn)在是 0,到了初始化的時(shí)候,才會(huì)真正把i 賦值為8
類加載器
類加載器負(fù)責(zé)加載所有的類,并且為載入內(nèi)存中的類生成一個(gè) java.lang.Class實(shí)例對(duì)象,如果一個(gè)類被加載到JVM中后,同一個(gè)類不會(huì)再次被載入,就像對(duì)象有一個(gè)唯一的標(biāo)識(shí),同樣載入的JVM的類也有一個(gè)唯一的標(biāo)識(shí)。JVM本身有一個(gè)類加載器的層次,這個(gè)類加載器本身就是一個(gè)普通的Class,所有的Class都是被類加載器加載到內(nèi)存中,我們可以稱之為ClassLoader,一個(gè)頂級(jí)的父類,也是一個(gè)abstract抽象類。
Bootstrap: 類加載器的加載過(guò)程,分成不同的層次來(lái)進(jìn)行加載,不同的類加載器加載不同的Class,作為最頂層的Bootstrap,它加載lib里JDK最核心的內(nèi)容,比如說(shuō)rt.jar charset.jar等核心類,當(dāng)我們調(diào)用getClassLoader()拿到這個(gè)加載器結(jié)果是一個(gè)Null的時(shí)候,代表我們已經(jīng)達(dá)到了最頂層的加載器
Extension:Extension加載器擴(kuò)展類,加載擴(kuò)展包里的各種各樣的文件,這些擴(kuò)展包在JDK安裝目錄 jre/lib/ext下的jar
App:就是我們平時(shí)用到的application ,用來(lái)加載classpath指定的內(nèi)容
Custom ClassLoader:自定義ClassLoader,加載自己自定義的加載器 Custom ClassLoader 的父類加載器是 application 的父類加載器是 Extension的父類加載器是Bootstrap
注意:他們不是繼承關(guān)系,而是委托關(guān)系
public class ClassLoaderTest { public static void main(String[] args) { // 查看是誰(shuí)Load到內(nèi)存的,執(zhí)行結(jié)果是null,因?yàn)锽ootstrap使用C++實(shí)現(xiàn)的 // 在Java里面沒(méi)有class和它對(duì)應(yīng) System.out.println(String.class.getClassLoader()); //這個(gè)是核心類庫(kù)某個(gè)包里的類執(zhí)行,執(zhí)行結(jié)果是Null,因?yàn)樵擃愐彩潜籅ootstrap加載的 System.out.println(sun.awt.HKSCS.class.getClassLoader()); //這個(gè)類是位于ext目錄下某個(gè)jar文件里面,當(dāng)我們調(diào)用他執(zhí)行結(jié)果就是sun.misc.Launcher$ExtClassLoader@a09ee92 System.out.println(sun.net.spi.nameservice.DNS.DNSNameService.class.getClassLoader()); // 這個(gè)是我們自己寫的ClassLoad加載器,由sun.misc.Launcher$AppClassLoader@18b4aac2加載 System.out.println(ClassLoaderTest.class.getClassLoader()); // 是Exe的ClassLoader 調(diào)用它的getclass(),它本身也是一個(gè)class,調(diào)用它的getClassLoader,他的ClassLoader的ClassLoader就是我們的Bootstrap所以結(jié)果為Null System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader()); } }
類加載器繼承關(guān)系
這個(gè)圖講的是ClassLoader從語(yǔ)法上是從誰(shuí)繼承的,這個(gè)圖只是單純的一個(gè)語(yǔ)法關(guān)系,不是繼承關(guān)系,大家可以記住,和上面的類加載沒(méi)有一點(diǎn)關(guān)系,過(guò)分的大家其實(shí)可以忽略這個(gè)圖
雙親委派
父加載器:父加載器不是"類加載器的加載器",也不是"類加載器的父類加載器" 雙親委派是一個(gè)孩子向父親的方向,然后父親向孩子方向的雙親委派過(guò)程
當(dāng)一個(gè)類加載器收到了類加載請(qǐng)求時(shí)候,他會(huì)先嘗試從自定義里面去找,同時(shí)它內(nèi)部還維護(hù)了緩存,如果在緩存中找到了就直接返回結(jié)果,如果沒(méi)有找到,就向父類進(jìn)行委托,父類再去緩存中找,一直到最頂級(jí)的父類,如果這個(gè)時(shí)候還沒(méi)有從緩存中獲取到我們想要的結(jié)果,這個(gè)時(shí)候父親就說(shuō)我你這個(gè)事情,我辦不了,你要自己動(dòng),然后兒子就自己去查詢對(duì)應(yīng)的class類并加載,如果到了最小的一個(gè)兒子還是沒(méi)有找到對(duì)應(yīng)的類,就會(huì)拋出異常 Class Not Found Exception

為什么要弄雙親委派?
這個(gè)是類加載器必問(wèn)的一個(gè)面試題。
主要為了安全,如果任何一個(gè)Class都可以把他load到內(nèi)存中的話,那么我寫一個(gè) java.lang.String,如果我寫入了有危險(xiǎn)的代碼,是不是就會(huì)發(fā)生安全問(wèn)題,并且可以保證Java核心api中定義的類型不會(huì)被隨意替換,可以防止API內(nèi)庫(kù)被隨意更改,其次是效率問(wèn)題,如果有緩存在,直接從緩存里面拿,就不用一遍一遍的去遍歷查詢我們的父類或者子類了。
到此,相信大家對(duì)“JVM加載機(jī)制是什么”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
網(wǎng)站欄目:JVM加載機(jī)制是什么
本文鏈接:http://fisionsoft.com.cn/article/jpeopc.html