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