聊一聊Java的JVM類加載機(jī)制
Java虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型,這個過程被稱作虛擬機(jī)的類加載機(jī)制。當(dāng)Java程序運(yùn)行時(shí),Java虛擬機(jī)會按需加載類,即在程序需要使用某個類時(shí)才會加載該類。
類的生命周期如下圖:
JVM的類加載機(jī)制包括加載、連接( 驗(yàn)證、準(zhǔn)備、解析)、初始化 3個階段。
加載(Loading)
加載(Loading) 階段主要是查找并加載字節(jié)碼文件,這個文件可以是來自本地文件系統(tǒng)、網(wǎng)絡(luò)、jar包等地方。加載后,生成一個對應(yīng)的Class對象。
加載類時(shí)會做以下工作:
- 根據(jù)類的全限定名查找并讀取類的二進(jìn)制數(shù)據(jù)。類的二進(jìn)制數(shù)據(jù)可以來自文件、網(wǎng)絡(luò)、數(shù)據(jù)庫等各種數(shù)據(jù)源。
- 將類的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成方法區(qū)內(nèi)部的數(shù)據(jù)結(jié)構(gòu)。在轉(zhuǎn)換的過程中,JVM會對類的二進(jìn)制數(shù)據(jù)進(jìn)行解析和校驗(yàn)。
- 在方法區(qū)內(nèi)存儲該類的相關(guān)信息,包括類的名稱、修飾符、常量池、字段描述符、方法描述符、接口描述符、方法表等。
- 生成一個代表該類的Class對象,并將該對象存放在JVM的堆內(nèi)存中。Class對象包含了類的各種信息,可以用于創(chuàng)建類的實(shí)例、獲取類的方法和字段等操作。
需要注意的是,在加載類的過程中,JVM會遵循一定的雙親委派機(jī)制,即先委派給父類加載器嘗試加載,如果父類加載器無法加載,則由當(dāng)前類加載器進(jìn)行加載。這樣可以保證類的加載不會重復(fù),避免出現(xiàn)類似的類被多次加載的情況。有關(guān)類加載器可以查看我之前的文章。
加載階段與連接階段的部分動作(如一部分字節(jié)碼文件格式驗(yàn)證動作)是交叉進(jìn)行的,加載階段尚未完成,連接階段可能已經(jīng)開始,但這些夾在加載階段之中進(jìn)行的動作,仍然屬于連接階段的一部分,這兩個階段的開始時(shí)間仍然保持著固定的先后順序。
連接(Linking)
連接階段是Java虛擬機(jī)將類文件中的符號引用轉(zhuǎn)換為直接引用的過程,會對字節(jié)碼文件進(jìn)行驗(yàn)證、準(zhǔn)備、解析。
- 驗(yàn)證(Verification):在這個階段,JVM會對字節(jié)碼進(jìn)行驗(yàn)證,以確保其符合Java虛擬機(jī)規(guī)范,并且不會對虛擬機(jī)造成安全威脅。驗(yàn)證的內(nèi)容包括靜態(tài)分析、字節(jié)碼驗(yàn)證、符號引用驗(yàn)證等。如果驗(yàn)證失敗,JVM會拋出VerifyError異常。
- 準(zhǔn)備(Preparation):在這個階段,JVM會為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認(rèn)值(零值)。這個階段不會執(zhí)行任何Java代碼,只是為靜態(tài)變量分配內(nèi)存空間。例如將int類型的靜態(tài)變量賦值為0。
public static int staticValue = 123;
準(zhǔn)備階段初始化只是將靜態(tài)變量初始化為默認(rèn)值,比如上面這段代碼,不同數(shù)據(jù)類型都有其默認(rèn)值,初始化是只是將staticValue
賦予默認(rèn)值0
,也就是staticValue = 0
,只有在初始化階段才會將staticValue
賦值為123,也就是staticValue = 123
。但是如果是staticValue是個常量public static final int staticValue = 123
,準(zhǔn)備階段才會將staticValue
賦值為123。
- 解析(Resolution):在這個階段,JVM會將類、接口、字段和方法的符號引用解析為直接引用。符號引用是指用來描述某個類、字段或方法的名稱和類型的符號,而直接引用則是指直接指向內(nèi)存中的具體位置的引用。解析的過程包括將類或接口的符號引用解析為直接引用、將字段的符號引用解析為直接引用、將類中方法的符號引用解析為直接引用。
初始化(Initialization)
初始化階段是指在類被首次主動使用時(shí)執(zhí)行的階段,虛擬機(jī)會執(zhí)行類的初始化代碼,包括靜態(tài)變量的賦值和靜態(tài)代碼塊的執(zhí)行等操作。
初始化階段是類加載的最后一個階段,在該階段,JVM會執(zhí)行以下操作:
- 執(zhí)行靜態(tài)變量賦值操作:在這個階段,JVM會對所有的靜態(tài)變量進(jìn)行初始化并賦值。這些靜態(tài)變量的值通常在類定義中已經(jīng)被明確定義了,JVM會根據(jù)定義進(jìn)行相應(yīng)的賦值操作。
- 執(zhí)行靜態(tài)代碼塊:如果類定義中包含有靜態(tài)代碼塊,那么在該階段JVM會執(zhí)行這些靜態(tài)代碼塊中的代碼。
- 調(diào)用類的初始化方法:在Java程序中,可以使用static關(guān)鍵字來定義一個類的靜態(tài)初始化方法(即static void methodName())。在該階段,JVM會調(diào)用這個類的靜態(tài)初始化方法。
類初始化的時(shí)機(jī)
類初始化時(shí)機(jī)包括以下四種情況:
- 創(chuàng)建該類的實(shí)例對象時(shí),例如使用 new 關(guān)鍵字創(chuàng)建對象,類的初始化將被觸發(fā) 如果一個類是程序執(zhí)行的入口類(即包含 main() 方法的類),那么也會觸發(fā)該類的初始化操作。
- 子類初始化會觸發(fā)父類初始化:當(dāng)一個子類被初始化時(shí),其父類也會被初始化。這意味著,如果一個類沒有被使用,那么它的父類也不會被初始化。
- 當(dāng)調(diào)用類的靜態(tài)方法(不包括final方法和private方法)或訪問類的靜態(tài)字段(不包括final字段)時(shí),類的初始化將被觸發(fā)。
- 當(dāng)使用反射API對類進(jìn)行某些操作時(shí)(例如使用Class.forName()方法加載類、調(diào)用Class.newInstance()方法創(chuàng)建對象、調(diào)用Method.invoke()方法調(diào)用方法等),類的初始化將被觸發(fā)。
初始化是線程安全的JVM保證一個類的初始化只會由一個線程去執(zhí)行,其他線程需要等待該線程完成后才能訪問該類。
下面用一個簡單的Java代碼示例,展示JVM類加載機(jī)制中初始化階段的示例
public class MyClass { // 靜態(tài)變量 public static String staticStr = "Hello, world!"; static { System.out.println("MyClass is initialized."); } // 構(gòu)造方法 public MyClass() { System.out.println("MyClass constructor is called."); } // 靜態(tài)方法 public static void staticMethod() { System.out.println("MyClass staticMethod is called."); } }
在上述代碼中,類MyClass
包含一個靜態(tài)變量staticStr
、一個靜態(tài)代碼塊和一個構(gòu)造方法,以及一個靜態(tài)方法staticMethod
。當(dāng)程序首次使用MyClass
類時(shí),JVM將會觸發(fā)MyClass類的初始化階段??梢酝ㄟ^下面的代碼來測試類的初始化:
public class Test { public static void main(String[] args) { System.out.println(MyClass.staticStr); // 調(diào)用靜態(tài)變量,觸發(fā)類初始化 MyClass.staticMethod(); // 調(diào)用靜態(tài)方法,觸發(fā)類初始化 MyClass obj = new MyClass(); // 創(chuàng)建對象,觸發(fā)類初始化 } }
在上面的代碼中,首先輸出了MyClass類的靜態(tài)變量staticStr
,此時(shí)會觸發(fā)MyClass類的初始化;然后調(diào)用了靜態(tài)方法staticMethod
,同樣會觸發(fā)MyClass類的初始化;最后創(chuàng)建了一個MyClass對象,也會觸發(fā)MyClass類的初始化。運(yùn)行上述代碼,可以看到以下輸出:
MyClass is initialized. Hello, world! MyClass staticMethod is called. MyClass constructor is called.
輸出結(jié)果表明,MyClass類的初始化確實(shí)在首次使用該類時(shí)被觸發(fā),包括靜態(tài)變量、靜態(tài)代碼塊、靜態(tài)方法和構(gòu)造方法都被執(zhí)行了。
此外,如果一個類是另一個類的子類,那么在使用子類時(shí),父類也會被初始化。例如:
public class MyBaseClass { static { System.out.println("MyBaseClass is initialized."); } } public class MySubClass extends MyBaseClass { static { System.out.println("MySubClass is initialized."); } } public class Test { public static void main(String[] args) { MySubClass obj = new MySubClass(); // 創(chuàng)建子類對象,觸發(fā)父類和子類初始化 } }
在上述代碼中,當(dāng)創(chuàng)建MySubClass類的對象時(shí),將會觸發(fā)MyBaseClass和MySubClass類的初始化。運(yùn)行上述代碼,可以看到以下輸出:
MyBaseClass is initialized. MySubClass is initialized.
總結(jié)
JVM的類加載機(jī)制采用了延遲加載的策略,即在需要使用類時(shí)才加載該類,這種方式可以提高程序的啟動速度,也避免了不必要的資源浪費(fèi)。同時(shí),JVM還提供了多個類加載器,可以通過自定義類加載器實(shí)現(xiàn)特定的加載策略,例如動態(tài)加載、遠(yuǎn)程加載等,從而滿足不同的應(yīng)用需求。
到此這篇關(guān)于聊一聊Java的JVM類加載機(jī)制的文章就介紹到這了,更多相關(guān)JVM類加載機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA技術(shù)實(shí)現(xiàn)上傳下載文件到FTP服務(wù)器(完整)
這篇文章主要介紹了JAVA技術(shù)實(shí)現(xiàn)上傳下載文件到FTP服務(wù)器(完整),本文使用 Apache Jakarta Commons Net(commons-net-3.3.jar) 基于FileZilla Server服務(wù)器實(shí)現(xiàn)FTP服務(wù)器上文件的上傳/下載/刪除等操作,需要的朋友可以參考下2015-07-07Java異常簡介和架構(gòu)_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要分享了Java異常簡介和架構(gòu),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Java設(shè)計(jì)模式開發(fā)中使用觀察者模式的實(shí)例教程
這篇文章主要介紹了Java設(shè)計(jì)模式開發(fā)中使用觀察者模式的實(shí)例教程,松耦合和邏輯清晰的消息監(jiān)聽是觀察者模式的大特色,需要的朋友可以參考下2016-04-04System.getProperty(“l(fā)ine.separator“)含義及意義詳解
這篇文章主要介紹了System.getProperty(“l(fā)ine.separator“)含義,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05關(guān)于JAVA11中圖片與BASE64相互轉(zhuǎn)換的實(shí)現(xiàn)
這篇文章主要介紹了關(guān)于JAVA11中圖片與BASE64相互轉(zhuǎn)換的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04Mybatis反射核心類Reflector的實(shí)現(xiàn)
本文主要介紹了Mybatis反射核心類Reflector的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11SpringBoot跨系統(tǒng)單點(diǎn)登陸的實(shí)現(xiàn)方法
這篇文章主要介紹了SpringBoot跨系統(tǒng)單點(diǎn)登陸的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08