分析JVM的執行子系統
一、Class類文件結構
1.1、JVM的平臺無關性
與平臺無關性是建立在操作系統上,虛擬機廠商提供瞭許多可以運行在各種不同平臺的虛擬機,它們都可以載入和執行字節碼,從而實現程序的一次編寫,到處運行。
各種不同平臺的虛擬機與所有平臺都統一使用的程序存儲格式——字節碼(ByteCode)是構成平臺無關性的基石,也是語言無關性的基礎。Java 虛擬機不和包括 Java 在內的任何語言綁定,它隻與“Class 文件”這種特定的二進制文件格式所關聯,Class 文件中包含瞭 Java 虛擬機指令集和符號表以及若幹其他輔助信息。
1.2、Class類文件
- Class文件是一組以8位字節為基礎單位的二進制流。
- 類似於結構體的偽結構來存儲數據。
- 隻有兩種數據類型:無符號數和表。
- 無符號數屬於基本的數據類型,以u1、u2、u4、u8 。
- 表是由多個無符號數或者其他表作為數據項構成的復合數據類型。
其中值得註意的一個東西,class文件中有顯示編譯的版本號,使用notepad++等工具打開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)。
2.1、加載
虛擬機需要完成以下 3 件事情:
1、通過一個類的全限定名來獲取定義此類的二進制字節流。
2、將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。
3、在內存中生成一個代表這個類的 java.lang.Class 對象,作為方法區這個類的各種數據的訪問入口。
2.2、驗證
是連接階段的第一步,這一階段的目的是為瞭確保 Class 文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。但從整體上看,驗證階段大致上會完成下面 4 個階段的檢驗動作:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。
2.3、準備階段
為類變量分配內存並設置類變量初始值(零值)的階段。
2.4、解析階段
是虛擬機將常量池內的符號引用替換為直接引用的過程。
2.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 關鍵字做對象所屬關系判定等情況。
3.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程序穩定運行。
3.2、Tomcat是怎麼保證兩個應用相同名稱類的隔離性
tomcat下可能會同時部署多個應用,那麼tomcat是怎麼保證多個應用相同類(比如 SysUserService 類)呢?
- Tomcat 本身也是一個 java 項目,因此其也需要被 JDK 的類加載機制加載,也就必然存在啟動類加載器、擴展類加載器和應用系統類加載器。
- 為瞭保證多應用相同類的唯一性,tomcat在一定程度上打破瞭雙親委派模型,每啟動一個項目都會創建一個唯一的WebApp類加載器,分別用來加載不同的應用,所以就保證瞭類的唯一性。
以上就是分析JVM的執行子系統的詳細內容,更多關於JVM 執行子系統的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Java基礎之ClassLoader詳解
- java類加載機制、類加載器、自定義類加載器的案例
- ClassLoader雙親委派模式作用詳解
- 一篇文章帶你深入瞭解Java類加載
- Java之類加載機制案例講解