JVM 的執行子系統

IT王小二發表於2021-06-13

JVM 的執行子系統。

一、Class類檔案結構

1. JVM的平臺無關性

與平臺無關性是建立在作業系統上,虛擬機器廠商提供了許多可以執行在各種不同平臺的虛擬機器,它們都可以載入和執行位元組碼,從而實現程式的一次編寫,到處執行。

各種不同平臺的虛擬機器與所有平臺都統一使用的程式儲存格式——位元組碼(ByteCode)是構成平臺無關性的基石,也是語言無關性的基礎。Java 虛擬機器不和包括 Java 在內的任何語言繫結,它只與“Class 檔案”這種特定的二進位制檔案格式所關聯,Class 檔案中包含了 Java 虛擬機器指令集和符號表以及若干其他輔助資訊。

2. Class類檔案

  • Class檔案是一組以8位位元組為基礎單位的二進位制流。
  • 類似於結構體的偽結構來儲存資料。
  • 只有兩種資料型別:無符號數和表。
  • 無符號數屬於基本的資料型別,以u1、u2、u4、u8 。
  • 表是由多個無符號數或者其他表作為資料項構成的複合資料型別。

其中值得注意的一個東西,class檔案中有顯示編譯的版本號,使用notepad++等工具開啟class檔案。

class檔案結構

圖中標記前四個被稱為魔數(唯一作用是確定這個檔案是否為一個能被虛擬機器接受的 Class 檔案)。

後兩個標記代表class檔案的版本號,其中第4第500 00位元組代表著jdk的次版本號,第6第7個位元組00 34代表這jdk的主版本號,Java 的版本號是從 45 開始的,JDK 1.1 之後的每個 JDK 大版本釋出主版本號向上加 1 高版本的 JDK 能向下相容以前版本的 Class 檔案,但不能執行以後版本的 Class 檔案,即使檔案格式並未發生任何變化,虛擬機器也必須拒絕執行超過其版本號的 Class 檔案。

34為16進位制,轉化為10進位制就是52,所以 52-45+1 , 代表這個class檔案的版本號為jdk1.8 。

當然class檔案的結構詳細說起來還有常量池、訪問標誌、父索引、介面索引、欄位表集合、方法表集合、屬性表集合,這些以後有時間再補上吧,概念性東西,對實際開發程式碼,優化程式碼幫助不大。

二、類的載入機制

類從被載入到虛擬機器記憶體中開始,到解除安裝出記憶體為止,它的整個生命週期包括:載入(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和解除安裝(Unloading)7 個階段。其中驗證、準備、解析 3 個部分統稱為連線(Linking)。

1. 載入

虛擬機器需要完成以下 3 件事情:

1、通過一個類的全限定名來獲取定義此類的二進位制位元組流。

2、將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構。

3、在記憶體中生成一個代表這個類的 java.lang.Class 物件,作為方法區這個類的各種資料的訪問入口。

2. 驗證

是連線階段的第一步,這一階段的目的是為了確保 Class 檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全。但從整體上看,驗證階段大致上會完成下面 4 個階段的檢驗動作:檔案格式驗證、後設資料驗證、位元組碼驗證、符號引用驗證。

3. 準備階段

為類變數分配記憶體並設定類變數初始值(零值)的階段。

4. 解析階段

是虛擬機器將常量池內的符號引用替換為直接引用的過程。

5. 初始化階段

是類載入過程的最後一步,前面的類載入過程中,除了在載入階段使用者應用程式可以通過自定義類載入器參與之外,其餘動作完全由虛擬機器主導和控制。

到了初始化階段,才真正開始執行類中定義的 Java 程式程式碼在準備階段,變數已經賦過一次系統要求的初始值,而在初始化階段,則根據程式設計師通過程。

序制定的主觀計劃去初始化類變數和其他資源(即呼叫類構造器之類的)。

5種情況會對類立即進行初始化(瞭解即可)

1、遇到 new、getstatic、putstatic 或 invokestatic 這 4 條位元組碼指令時,如果類沒有進行過初始化,則需要先觸發其初始化。生成這 4 條指令的最常見的Java 程式碼場景是:使用 new 關鍵字例項化物件的時候、讀取或設定一個類的靜態欄位(被 final 修飾、已在編譯期把結果放入常量池的靜態欄位除外)的時候,以及呼叫一個類的靜態方法的時候。
2、使用 java.lang.reflect 包的方法對類進行反射呼叫的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
3、當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
4、當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含 main()方法的那個類),虛擬機器會先初始化這個主類。
5、當使用 JDK 1.7 的動態語言支援時,如果一個 java.lang.invoke.MethodHandle 例項最後的解析結果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法控制程式碼,並且這個方法控制程式碼所對應的類沒有進行過初始化,則需要先觸發其初始化。

三、類載入器

對於任意一個類,都需要由 載入它的類載入器和這個類本身一同確立其在 Java 虛擬機器中的唯一性 ,每一個類載入器,都擁有一個獨立的類名稱空間。這句話可以表達得更通俗一些:比較兩個類是否“相等”,只有在這兩個類是由同一個類載入器載入的前提下才有意義,否則,即使這兩個類來源於同一個 Class 檔案,被同一個虛擬機器載入,只要載入它們的類載入器不同,那這兩個類就必定不相等。這裡所指的“相等”,包括代表類的 Class 物件的 equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果,也包括使用 instanceof 關鍵字做物件所屬關係判定等情況。

1. 雙親委派模型

  • 啟動類載入器 : BootstrapClassLoader,負責載入存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被 -Xbootclasspath引數指定的路徑中的,並且能被虛擬機器識別的類庫(如rt.jar,所有的java.開頭的類均被 BootstrapClassLoader載入)。啟動類載入器是無法被Java程式直接引用的。
  • 擴充套件類載入器 : ExtensionClassLoader,該載入器由sun.misc.Launcher$ExtClassLoader實現,它負責載入 JDK\jre\lib\ext目錄中,或者由java.ext.dirs系統變數指定的路徑中的所有類庫(如javax.開頭的類),開發者可以直接使用擴充套件類載入器。
  • 應用類載入器 : ApplicationClassLoader,該類載入器由 sun.misc.Launcher$AppClassLoader來實現,它負責載入使用者類路徑(ClassPath)所指定的類,開發者可以直接使用該類載入器,如果應用程式中沒有自定義過自己的類載入器,一般情況下這個就是程式中預設的類載入器。
  • 自定義ClassLoader :在自定義 ClassLoader 的子類時候,我們常見的會有兩種做法,一種是重寫 loadClass 方法,另一種是重寫 findClass 方法。
    • 其實這兩種方法本質上差不多畢竟 loadClass 也會呼叫 findClass,但是從邏輯上講我們最好不要直接修改 loadClass 的內部邏輯。
    • 建議的做法是隻在 findClass 裡重寫自定義類的載入方法。loadClass 這個方法是實現雙親委託模型邏輯的地方,擅自修改這個方法會導致模型被破壞,容易造成問題。
    • 因此我們最好是在雙親委託模型框架內進行小範圍的改動,不破壞原有的穩定結構。同時,也避免了自己重寫 loadClass 方法的過程中必須寫雙親委託的重複程式碼,從程式碼的複用性來看,不直接修改這個方法始終是比較好的選擇。。

雙親委派模型

雙親委派模型的過程

1、當 AppClassLoader載入一個class時,它首先不會自己去嘗試載入這個類,而是把類載入請求委派給父類載入器ExtClassLoader去完成。
2、當 ExtClassLoader載入一個class時,它首先也不會自己去嘗試載入這個類,而是把類載入請求委派給BootStrapClassLoader去完成。
3、如果 BootStrapClassLoader載入失敗(例如在 $JAVA_HOME/jre/lib裡未查詢到該class),會使用 ExtClassLoader來嘗試載入。
4、若ExtClassLoader也載入失敗,則會使用 AppClassLoader來載入,如果 AppClassLoader也載入失敗,則會報出異常 ClassNotFoundException。

雙親委派模型的好處

Java類隨著它的類載入器一起具備了帶有優先順序的層次關係,保證java程式穩定執行。

2. Tomcat是怎麼保證兩個應用相同名稱類的隔離性

tomcat機制

tomcat下可能會同時部署多個應用,那麼tomcat是怎麼保證多個應用相同類(比如 SysUserService 類)呢?

  • Tomcat 本身也是一個 java 專案,因此其也需要被 JDK 的類載入機制載入,也就必然存在啟動類載入器、擴充套件類載入器和應用系統類載入器。
  • 為了保證多應用相同類的唯一性,tomcat在一定程度上打破了雙親委派模型,每啟動一個專案都會建立一個唯一的WebApp類載入器,分別用來載入不同的應用,所以就保證了類的唯一性。

都讀到這裡了,來個 點贊、評論、關注、收藏 吧!

文章作者:IT王小二
首發地址:https://www.itwxe.com/posts/bad8f059/
版權宣告:文章內容遵循 署名-非商業性使用-禁止演繹 4.0 國際 進行許可,轉載請在文章頁面明顯位置給出作者與原文連結。

相關文章