JVM虛擬機(jī)的類加載機(jī)制詳解
一、類的生命周期
類是在運(yùn)行期間第一次使用時(shí)動(dòng)態(tài)加載的,而不是一次性加載所有類。因?yàn)槿绻淮涡约虞d,那么會(huì)占用很多的內(nèi)存。
包括以下 7 個(gè)階段:
- 加載(Loading)
- 驗(yàn)證(Verification)
- 準(zhǔn)備(Preparation)
- 解析(Resolution)
- 初始化(Initialization)
- 使用(Using)
- 卸載(Unloading)
二、類的加載過程
包含了加載、驗(yàn)證、準(zhǔn)備、解析和初始化這 5 個(gè)階段。
加載
加載過程完成以下三件事:
- 通過類的完全限定名稱獲取定義該類的二進(jìn)制字節(jié)流。
- 將該字節(jié)流表示的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)存儲(chǔ)結(jié)構(gòu)。
- 在內(nèi)存中生成一個(gè)代表該類的 Class 對(duì)象,作為方法區(qū)中該類各種數(shù)據(jù)的訪問入口。
其中二進(jìn)制字節(jié)流可以從以下方式中獲?。?/p>
- 從 ZIP 包讀取,成為 JAR、EAR、WAR 格式的基礎(chǔ)。
- 從網(wǎng)絡(luò)中獲取,最典型的應(yīng)用是 Applet。
- 運(yùn)行時(shí)計(jì)算生成,例如動(dòng)態(tài)代理技術(shù),在 java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass 的代理類的二進(jìn)制字節(jié)流。
- 由其他文件生成,例如由 JSP 文件生成對(duì)應(yīng)的 Class 類。
驗(yàn)證
JVM 會(huì)在該階段對(duì)二進(jìn)制字節(jié)流進(jìn)行校驗(yàn),只有符合 JVM 字節(jié)碼規(guī)范的才能被 JVM 正確執(zhí)行。該階段是保證 JVM 安全的重要屏障,下面是一些主要的檢查。
- 確保二進(jìn)制字節(jié)流格式符合預(yù)期(比如說是否以 cafe bene 開頭)。
- 是否所有方法都遵守訪問控制關(guān)鍵字的限定。
- 方法調(diào)用的參數(shù)個(gè)數(shù)和類型是否正確。
- 確保變量在使用之前被正確初始化了。
- 檢查變量是否被賦予恰當(dāng)類型的值。
準(zhǔn)備
JVM 會(huì)在該階段對(duì)類變量(也稱為靜態(tài)變量,static 關(guān)鍵字修飾的)分配內(nèi)存并初始化(對(duì)應(yīng)數(shù)據(jù)類型的默認(rèn)初始值,如 0、0L、null、false 等)。
此時(shí)不會(huì)分配實(shí)例變量的內(nèi)存,因?yàn)閷?shí)例變量是在實(shí)例化對(duì)象時(shí)一起創(chuàng)建在Java 堆中的。而且此時(shí)類變量是賦值為零值,即 int 類型的零值為 0,引用類型零值為 null,而不是代碼中顯示賦值的數(shù)值。
解析
該階段將常量池中的符號(hào)引用轉(zhuǎn)化為直接引用。
在 class 文件中常量池里面存放了字面量和符號(hào)引用,符號(hào)引用包括類和接口的全限定名以及字段和方法的名稱與描述符。
在 JVM 動(dòng)態(tài)鏈接的時(shí)候需要根據(jù)這些符號(hào)引用來轉(zhuǎn)換為直接引用存放內(nèi)存使用。
初始化
該階段是類加載過程的最后一步。在準(zhǔn)備階段,類變量已經(jīng)被賦過默認(rèn)初始值,而在初始化階段,類變量將被賦值為代碼期望賦的值。換句話說,初始化階段是執(zhí)行類構(gòu)造器方法的過程。
三、類加載時(shí)機(jī)
- new、getstatic、putstatic、invokestatic 這 4 個(gè)字節(jié)碼指令時(shí)對(duì)類進(jìn)行初始化(即:實(shí)例化對(duì)象、讀寫靜態(tài)對(duì)象、調(diào)用靜態(tài)方法時(shí),進(jìn)行類的初始化);
- 使用反射機(jī)制對(duì)類進(jìn)行調(diào)用時(shí),進(jìn)行類的初始化;
- 初始化一個(gè)類,其父類沒有初始化時(shí),先初始化其父類;
- 虛擬機(jī)啟動(dòng)時(shí),初始化一個(gè)執(zhí)行主類;
- 使用 JDK1.7 的動(dòng)態(tài)語言支持時(shí),如果 MethodHandle 實(shí)例的解析結(jié)果為 REF_getstatic、REF_putstatic、REF_invokestatic 的方法句柄(即:讀寫靜態(tài)對(duì)象或者調(diào)用靜態(tài)方法),則初始化該句柄對(duì)應(yīng)類。
四、類加載器分類
講到類加載不得不講到類加載的順序和類加載器。
Java 中大概有四種類加載器,分別是:啟動(dòng)類加載器(Bootstrap ClassLoader),擴(kuò)展類加載器(Extension ClassLoader),系統(tǒng)類加載器(System ClassLoader),自定義類加載器(Custom ClassLoader),依次屬于繼承關(guān)系(注意這里的繼承不是 Java 類里面的 extends)
- 啟動(dòng)類加載器(Bootstrap ClassLoader):主要負(fù)責(zé)加載存放在Java_Home/jre/lib下,或被-Xbootclasspath參數(shù)指定的路徑下的,并且能被虛擬機(jī)識(shí)別的類庫(如rt.jar,所有的java.*開頭的類均被Bootstrap ClassLoader加載),啟動(dòng)類加載器是無法被Java程序直接引用的。
- 擴(kuò)展類加載器(Extension ClassLoader):主要負(fù)責(zé)加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),它負(fù)責(zé)加載Java_Home/jre/lib/ext目錄中,或者由java.ext.dirs系統(tǒng)變量指定的路徑中的所有類庫(如javax.*開頭的類),開發(fā)者可以直接使用擴(kuò)展類加載器。
- 系統(tǒng)類加載器(System ClassLoader):主要負(fù)責(zé)加載器由sun.misc.Launcher$AppClassLoader來實(shí)現(xiàn),它負(fù)責(zé)加載用戶類路徑(ClassPath)所指定的類,開發(fā)者可以直接使用該類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。
- 自定義類加載器(Custom ClassLoader:自己開發(fā)的類加載器。
五、雙親委派原則
類加載器在加載 class 文件的時(shí)候,遵從雙親委派原則,意思是加載依次由父加載器先執(zhí)行加載動(dòng)作,只有當(dāng)父加載器沒有加載到 class 文件時(shí)才由子類加載器進(jìn)行加載。這種機(jī)制很好的保證了 Java API 的安全性,使得 JDK 的代碼不會(huì)被篡改。
六、Java字節(jié)碼文件中的JVM指令
1、創(chuàng)建一個(gè) Java 源文件 Test02.java,并在 main 方法中完成簡單的邏輯操作,如下所示。
public class Test02 { public static void main(String[] args) { int i = 5; int j = 10; int k = i + j; System.out.println(k); } }
2、在終端通過 javac 命令編譯 HelloWorld.java。
javac Test02.java
3、反編譯成我們能看懂的 JVM 指令,這里我們使用 javap -c 命令完成。
javap -c Test02.class
4、反編譯之后的 JVM 指令如下所示。
Compiled from "Test02.java" public class org.example.jvm.Test02 { public org.example.jvm.Test02(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: iconst_5 1: istore_1 2: bipush 10 4: istore_2 5: iload_1 6: iload_2 7: iadd 8: istore_3 9: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 12: iload_3 13: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 16: return }
解釋一下上述的 JVM 指令:
第 1 行表示當(dāng)前的字節(jié)碼文件編譯自 Test02.java。
第 3 行表示調(diào)用 Test02的無參構(gòu)造函數(shù)來實(shí)例化當(dāng)前對(duì)象。
第 4 行到第 7 行表示無參構(gòu)造函數(shù)的執(zhí)行流程。
第 5 行表示把 this 壓入操作數(shù)棧中。
第 6 行表示調(diào)用 Test02父類 Object 的無參構(gòu)造,我們知道每個(gè)對(duì)象在實(shí)例化的時(shí)候都會(huì)默認(rèn)先實(shí)例化其父類對(duì)象,并且默認(rèn)調(diào)用父類的無參構(gòu)造。
第 7 行 return 表示構(gòu)造方法執(zhí)行完畢。
第 10 行到第 22 行表示 main 方法的執(zhí)行流程。
第 11 行表示將常量 5 壓入操作數(shù)棧。
第 12 行表示取出操作數(shù)棧棧頂元素,即 5,然后保存到局部變量表第 1 個(gè)位置,即變量 i。
第 13 行表示將常量 10 壓入操作數(shù)棧。
第 14 行表示取出操作數(shù)棧棧頂元素,即 10,然后保存到局部變量表第 2 個(gè)位置,即變量 j。
第 15 行表示將局部變量表第 1 個(gè)變量(i)壓入操作數(shù)棧。
第 16 行表示將局部變量表第 2 個(gè)變量(j)壓入操作數(shù)棧。
第 17 行表示取出操作數(shù)棧中的前兩個(gè)值相加,并將結(jié)果壓入操作數(shù)棧頂。
第 18 行表示取出操作數(shù)棧棧頂元素,保存到局部變量表第 3 個(gè)位置,即變量 k。
第 19 行表示讀取靜態(tài)實(shí)例 PrintStream。
第 20 行表示將局部變量表第 3 個(gè)變量(k)壓入操作數(shù)棧。
第 21 行表示調(diào)用 PrintStream 的 println 方法,將操作數(shù)棧頂元素(變量 k)輸出。
第 22 行 return 表示 main 方法執(zhí)行完畢。
到此這篇關(guān)于JVM虛擬機(jī)的類加載機(jī)制詳解的文章就介紹到這了,更多相關(guān)JVM類加載機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用原生JDBC動(dòng)態(tài)解析并獲取表格列名和數(shù)據(jù)的方法
這篇文章主要介紹了使用原生JDBC動(dòng)態(tài)解析并獲取表格列名和數(shù)據(jù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08SpringBoot?AOP統(tǒng)一處理Web請求日志的示例代碼
springboot有很多方法處理日志,例如攔截器,aop切面,service中代碼記錄等,下面這篇文章主要給大家介紹了關(guān)于SpringBoot?AOP統(tǒng)一處理Web請求日志的相關(guān)資料,需要的朋友可以參考下2023-02-02MyBatis綁定錯(cuò)誤提示BindingException:Invalid bound statement (not f
這篇文章主要介紹了MyBatis綁定錯(cuò)誤提示BindingException:Invalid bound statement (not found)的解決辦法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧2017-01-01SpringBoot項(xiàng)目啟動(dòng)錯(cuò)誤:找不到或無法加載主類的三種解決方法
在開發(fā)SpringBoot應(yīng)用時(shí),經(jīng)常可能會(huì)遇到一個(gè)啟動(dòng)錯(cuò)誤:“錯(cuò)誤:找不到或無法加載主類 com.example.controller.demo.DemoApplication”,本文將介紹三種解決這一問題的方法,需要的朋友可以參考下2024-10-10spring中使用@Autowired注解無法注入的情況及解決
這篇文章主要介紹了spring中使用@Autowired注解無法注入的情況及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09JMeter自定義日志與日志分析的實(shí)現(xiàn)
JMeter與Java程序一樣,會(huì)記錄事件日志,本文就介紹一下JMeter自定義日志與日志分析的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12在Java的Hibernate框架中對(duì)數(shù)據(jù)庫數(shù)據(jù)進(jìn)行查詢操作
這篇文章主要介紹了Java的Hibernate框架中對(duì)數(shù)據(jù)庫數(shù)據(jù)進(jìn)行查詢操作的方法,Hibernate是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2015-12-12