分析JVM的執(zhí)行子系統(tǒng)
一、Class類文件結(jié)構(gòu)
1.1、JVM的平臺無關(guān)性
與平臺無關(guān)性是建立在操作系統(tǒng)上,虛擬機廠商提供了許多可以運行在各種不同平臺的虛擬機,它們都可以載入和執(zhí)行字節(jié)碼,從而實現(xiàn)程序的一次編寫,到處運行。
各種不同平臺的虛擬機與所有平臺都統(tǒng)一使用的程序存儲格式——字節(jié)碼(ByteCode)是構(gòu)成平臺無關(guān)性的基石,也是語言無關(guān)性的基礎(chǔ)。Java 虛擬機不和包括 Java 在內(nèi)的任何語言綁定,它只與“Class 文件”這種特定的二進制文件格式所關(guān)聯(lián),Class 文件中包含了 Java 虛擬機指令集和符號表以及若干其他輔助信息。
1.2、Class類文件
- Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進制流。
- 類似于結(jié)構(gòu)體的偽結(jié)構(gòu)來存儲數(shù)據(jù)。
- 只有兩種數(shù)據(jù)類型:無符號數(shù)和表。
- 無符號數(shù)屬于基本的數(shù)據(jù)類型,以u1、u2、u4、u8 。
- 表是由多個無符號數(shù)或者其他表作為數(shù)據(jù)項構(gòu)成的復(fù)合數(shù)據(jù)類型。
其中值得注意的一個東西,class文件中有顯示編譯的版本號,使用notepad++等工具打開class文件。
圖中標記前四個被稱為魔數(shù)(唯一作用是確定這個文件是否為一個能被虛擬機接受的 Class 文件)。
后兩個標記代表class文件的版本號,其中第4第500 00
字節(jié)代表著jdk的次版本號,第6第7個字節(jié)00 34
代表這jdk的主版本號,Java 的版本號是從 45 開始的,JDK 1.1 之后的每個 JDK 大版本發(fā)布主版本號向上加 1 高版本的 JDK 能向下兼容以前版本的 Class 文件,但不能運行以后版本的 Class 文件,即使文件格式并未發(fā)生任何變化,虛擬機也必須拒絕執(zhí)行超過其版本號的 Class 文件。
34
為16進制,轉(zhuǎn)化為10進制就是52,所以 52-45+1 , 代表這個class文件的版本號為jdk1.8 。
當然class文件的結(jié)構(gòu)詳細說起來還有常量池、訪問標志、父索引、接口索引、字段表集合、方法表集合、屬性表集合,這些以后有時間再補上吧,概念性東西,對實際開發(fā)代碼,優(yōu)化代碼幫助不大。
二、類的加載機制
類從被加載到虛擬機內(nèi)存中開始,到卸載出內(nèi)存為止,它的整個生命周期包括:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7 個階段。其中驗證、準備、解析 3 個部分統(tǒng)稱為連接(Linking)。
2.1、加載
虛擬機需要完成以下 3 件事情:
1、通過一個類的全限定名來獲取定義此類的二進制字節(jié)流。
2、將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)。
3、在內(nèi)存中生成一個代表這個類的 java.lang.Class 對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。
2.2、驗證
是連接階段的第一步,這一階段的目的是為了確保 Class 文件的字節(jié)流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。但從整體上看,驗證階段大致上會完成下面 4 個階段的檢驗動作:文件格式驗證、元數(shù)據(jù)驗證、字節(jié)碼驗證、符號引用驗證。
2.3、準備階段
為類變量分配內(nèi)存并設(shè)置類變量初始值(零值)的階段。
2.4、解析階段
是虛擬機將常量池內(nèi)的符號引用替換為直接引用的過程。
2.5、初始化階段
是類加載過程的最后一步,前面的類加載過程中,除了在加載階段用戶應(yīng)用程序可以通過自定義類加載器參與之外,其余動作完全由虛擬機主導(dǎo)和控制。
到了初始化階段,才真正開始執(zhí)行類中定義的 Java 程序代碼在準備階段,變量已經(jīng)賦過一次系統(tǒng)要求的初始值,而在初始化階段,則根據(jù)程序員通過程。
序制定的主觀計劃去初始化類變量和其他資源(即調(diào)用類構(gòu)造器之類的)。
5種情況會對類立即進行初始化(了解即可)
1、遇到 new、getstatic、putstatic 或 invokestatic 這 4 條字節(jié)碼指令時,如果類沒有進行過初始化,則需要先觸發(fā)其初始化。生成這 4 條指令的最常見的Java 代碼場景是:使用 new 關(guān)鍵字實例化對象的時候、讀取或設(shè)置一個類的靜態(tài)字段(被 final 修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時候,以及調(diào)用一個類的靜態(tài)方法的時候。
2、使用 java.lang.reflect 包的方法對類進行反射調(diào)用的時候,如果類沒有進行過初始化,則需要先觸發(fā)其初始化。
3、當初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進行過初始化,則需要先觸發(fā)其父類的初始化。
4、當虛擬機啟動時,用戶需要指定一個要執(zhí)行的主類(包含 main()方法的那個類),虛擬機會先初始化這個主類。
5、當使用 JDK 1.7 的動態(tài)語言支持時,如果一個 java.lang.invoke.MethodHandle 實例最后的解析結(jié)果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且這個方法句柄所對應(yīng)的類沒有進行過初始化,則需要先觸發(fā)其初始化。
三、類加載器
對于任意一個類,都需要由 加載它的類加載器和這個類本身一同確立其在 Java 虛擬機中的唯一性 ,每一個類加載器,都擁有一個獨立的類名稱空間。這句話可以表達得更通俗一些:比較兩個類是否“相等”,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源于同一個 Class 文件,被同一個虛擬機加載,只要加載它們的類加載器不同,那這兩個類就必定不相等。這里所指的“相等”,包括代表類的 Class 對象的 equals()方法、isAssignableFrom()方法、isInstance()方法的返回結(jié)果,也包括使用 instanceof 關(guān)鍵字做對象所屬關(guān)系判定等情況。
3.1、雙親委派模型
- 啟動類加載器 : BootstrapClassLoader,負責加載存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被 -Xbootclasspath參數(shù)指定的路徑中的,并且能被虛擬機識別的類庫(如rt.jar,所有的java.開頭的類均被 BootstrapClassLoader加載)。啟動類加載器是無法被Java程序直接引用的。
- 擴展類加載器 : ExtensionClassLoader,該加載器由sun.misc.Launcher$ExtClassLoader實現(xiàn),它負責加載 JDK\jre\lib\ext目錄中,或者由java.ext.dirs系統(tǒng)變量指定的路徑中的所有類庫(如javax.開頭的類),開發(fā)者可以直接使用擴展類加載器。
- 應(yīng)用類加載器 : ApplicationClassLoader,該類加載器由 sun.misc.Launcher$AppClassLoader來實現(xiàn),它負責加載用戶類路徑(ClassPath)所指定的類,開發(fā)者可以直接使用該類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。
- 自定義ClassLoader :在自定義 ClassLoader 的子類時候,我們常見的會有兩種做法,一種是重寫 loadClass 方法,另一種是重寫 findClass 方法。其實這兩種方法本質(zhì)上差不多畢竟 loadClass 也會調(diào)用 findClass,但是從邏輯上講我們最好不要直接修改 loadClass 的內(nèi)部邏輯。建議的做法是只在 findClass 里重寫自定義類的加載方法。loadClass 這個方法是實現(xiàn)雙親委托模型邏輯的地方,擅自修改這個方法會導(dǎo)致模型被破壞,容易造成問題。因此我們最好是在雙親委托模型框架內(nèi)進行小范圍的改動,不破壞原有的穩(wěn)定結(jié)構(gòu)。同時,也避免了自己重寫 loadClass 方法的過程中必須寫雙親委托的重復(fù)代碼,從代碼的復(fù)用性來看,不直接修改這個方法始終是比較好的選擇。
雙親委派模型的過程:
1、當 AppClassLoader加載一個class時,它首先不會自己去嘗試加載這個類,而是把類加載請求委派給父類加載器ExtClassLoader去完成。
2、當 ExtClassLoader加載一個class時,它首先也不會自己去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader去完成。
3、如果 BootStrapClassLoader加載失敗(例如在 $JAVA_HOME/jre/lib里未查找到該class),會使用 ExtClassLoader來嘗試加載。
4、若ExtClassLoader也加載失敗,則會使用 AppClassLoader來加載,如果 AppClassLoader也加載失敗,則會報出異常 ClassNotFoundException。
雙親委派模型的好處:
Java類隨著它的類加載器一起具備了帶有優(yōu)先級的層次關(guān)系,保證java程序穩(wěn)定運行。
3.2、Tomcat是怎么保證兩個應(yīng)用相同名稱類的隔離性
tomcat下可能會同時部署多個應(yīng)用,那么tomcat是怎么保證多個應(yīng)用相同類(比如 SysUserService 類)呢?
- Tomcat 本身也是一個 java 項目,因此其也需要被 JDK 的類加載機制加載,也就必然存在啟動類加載器、擴展類加載器和應(yīng)用系統(tǒng)類加載器。
- 為了保證多應(yīng)用相同類的唯一性,tomcat在一定程度上打破了雙親委派模型,每啟動一個項目都會創(chuàng)建一個唯一的WebApp類加載器,分別用來加載不同的應(yīng)用,所以就保證了類的唯一性。
以上就是分析JVM的執(zhí)行子系統(tǒng)的詳細內(nèi)容,更多關(guān)于JVM 執(zhí)行子系統(tǒng)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringCloudGateway網(wǎng)關(guān)處攔截并修改請求的操作方法
這篇文章主要介紹了SpringCloudGateway網(wǎng)關(guān)處攔截并修改請求的操作方法,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧2023-12-12mybatis-plus數(shù)據(jù)權(quán)限實現(xiàn)代碼
這篇文章主要介紹了mybatis-plus數(shù)據(jù)權(quán)限實現(xiàn),結(jié)合了mybatis-plus的插件方式,做出了自己的注解方式的數(shù)據(jù)權(quán)限,雖然可能存在一部分的局限性,但很好的解決了我們自己去解析SQL的功能,需要的朋友可以參考下2023-06-06提交gRPC-spring-boot-starter項目bug修復(fù)的pr說明
這篇文章主要介紹了這篇文章主要為大家介紹了gRPC-spring-boot-starter項目提交bug修復(fù)的pr的原因說明,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-02-02SpringBoot處理大量請求數(shù)據(jù)的傳輸問題的方法小結(jié)
在Spring?Boot項目常常需要中處理大量請求數(shù)據(jù)的傳輸問題,這篇文章主要為大家整理了一些常用的方法,感興趣的小伙伴可以跟隨小編一起學習一下2024-01-01