JVM優化:JVM加載機制詳解——類裝載子系統
一、類加載子系統介紹
1、類加載子系統負責從文件系統或是網絡中加載.class文件,class文件在文件開頭有特定的文件標識。
2、把加載 后的class類信息存放于方法區,除了類信息之外,方法區還會存放運行時常量池信息,可能還包括字符串字面量和 數字常量(這部分常量信息是Class文件中常量池部分的內存映射);
3、ClassLoader只負責class文件的加載,至于 它是否可以運行,則由Execution Engine決定;
4、如果調用構造器實例化對象,則該對象存放在堆區;
二、類加載器ClassLoader角色
1. class file 存在于本地硬盤上,可以理解為設計師畫在紙上的模板,而最終這個模板在執行的時候是要加載到 JVM當中來根據這個文件實例化出n個一模一樣的實例。
2. class file 加載到JVM中,被稱為DNA元數據模板。
3. 在 .class文件 --> JVM --> 最終成為元數據模板,此過程就要一個運輸工具(類裝載器Class Loader),扮演一 個快遞員的角色。
三、類加載的執行過程
我們知道我們寫的程序經過編譯后成為了.class文件,.class文件中描述了類的各種信息,最終都需要加載到虛擬機 之后才能運行和使用。而虛擬機如何加載這些.class文件?.class文件的信息進入到虛擬機后會發生什么變化?
類使用的7個階段
類從被加載到虛擬機內存中開始,到卸載出內存,它的整個生命周期包括:加載(Loading)、驗證 (Verification)、準備(Preparation)、解析(Resolution)、初始化(Initiallization)、使用(Using)和卸載 (Unloading)這7個階段。其中驗證、準備、解析3個部分統稱為連接(Linking),這七個階段的發生順序如下 圖:
圖中,加載、驗證、準備、初始化、卸載這5個階段的順序是確定的,類的加載過程必須按照這種順序按部就班地 開始,而解析階段不一定:它在某些情況下可以初始化階段之后在開始,這是為了支持Java語言的運行時綁定(也 稱為動態綁定)。接下來講解加載、驗證、準備、解析、初始化五個步驟,這五個步驟組成了一個完整的類加載過 程。使用沒什么好說的,卸載屬于GC的工作 。
1、加載
加載是類加載的第一個階段。有兩種時機會觸發類加載:
1)預加載
虛擬機啟動時加載,加載的是JAVA_HOME/lib/下的rt.jar下的.class文件,這個jar包里面的內容是程序運行時非常常 常用到的,像java.lang.*、java.util.、java.io. 等等,因此隨著虛擬機一起加載。要證明這一點很簡單,寫一個空的 main函數,設置虛擬機參數為"-XX:+TraceClassLoading"來獲取類加載信息,運行一下:
2)運行時加載
虛擬機在用到一個.class文件的時候,會先去內存中查看一下這個.class文件有沒有被加載,如果沒有就會按照類的 全限定名來加載這個類。
那么,加載階段做了什么,其實加載階段做了有三件事情:
獲取.class文件的二進制流
將類信息、靜態變量、字節碼、常量這些.class文件中的內容放入方法區中
在內存中生成一個代表這個.class文件的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口。一般 這個Class是在堆里的,不過HotSpot虛擬機比較特殊,這個Class對象是放在方法區中的
虛擬機規范對這三點的要求并不具體,因此虛擬機實現與具體應用的靈活度都是相當大的。例如第一條,根本沒有 指明二進制字節流要從哪里來、怎么來,因此單單就這一條,就能變出許多花樣來:
- 從zip包中獲取,這就是以后jar、ear、war格式的基礎
- 從網絡中獲取,典型應用就是Applet
- 運行時計算生成,典型應用就是動態代理技術
- 由其他文件生成,典型應用就是JSP,即由JSP生成對應的.class文件
- 從數據庫中讀取,這種場景比較少見
總而言之,在類加載整個過程中,這部分是對于開發者來說可控性最強的一個階段。
2、鏈接
鏈接包含三個步驟: 分別是 驗證Verification , 準備Preparation , 解析Resolution 三個過程
1)驗證Verification
連接階段的第一步,這一階段的目的是為了確保.class文件的字節流中包含的信息符合當前虛擬機的要求,并且不 會危害虛擬機自身的安全。
Java語言本身是相對安全的語言(相對C/C++來說),但是前面說過,.class文件未必要從Java源碼編譯而來,可以 使用任何途徑產生,甚至包括用十六進制編輯器直接編寫來產生.class文件。在字節碼語言層面上,Java代碼至少從 語義上是可以表達出來的。虛擬機如果不檢查輸入的字節流,對其完全信任的話,很可能會因為載入了有害的字節 流而導致系統崩潰,所以驗證是虛擬機對自身保護的一項重要工作。
驗證階段將做一下幾個工作,具體就不細講了,這是虛擬機實現層面的問題:
- 文件格式驗證
- 元數據驗證
- 字節碼驗證
- 符號引用驗證
2)準備Preparation
準備階段是正式為類變量分配內存并設置其初始值的階段,這些變量所使用的內存都將在方法區中分配。關于這 點,有兩個地方注意一下:
- 這時候進行內存分配的僅僅是類變量(被static修飾的變量),而不是實例變量,實例變量將會在對象實例化 的時候隨著對象一起分配在Java堆中
- 這個階段賦初始值的變量指的是那些不被final修飾的static變量,比如"public static int value = 123",value在準 備階段過后是0而不是123,給value賦值為123的動作將在初始化階段才進行;比如"public static final int value = 123;"就不一樣了,在準備階段,虛擬機就會給value賦值為123。
各個數據類型的零值如下表:
3、解析Resolution
解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。來了解一下符號引用和直接引用有什么區別:
1)符號引用
符號引用是一種定義,可以是任何字面上的含義,而直接引用就是直接指向目標的指針、相對偏移量。 這個其實是屬于編譯原理方面的概念,符號引用包括了下面三類常量: 類和接口的全限定名 字段的名稱和描述符 方法的名稱和描述符
2)直接引用
直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是和虛擬機實現的 內存布局相關的,同一個符號引用在不同的虛擬機示例上翻譯出來的直接引用一般不會相同。如果有了直接引用, 那引用的目標必定已經存在在內存中了。
解析階段負責把整個類激活,串成一個可以找到彼此的網,過程不可謂不重要。那這個階段都做了哪些工作呢?大 體可以分為:
- 類或接口的解析
- 類方法解析
- 接口方法解析
- 字段解析
4、初始化
類的初始化階段是類加載過程的最后一個步驟, 之前介紹的幾個類加載的動作里, 除了在加載階段用戶應用程序 可以通過自定義類加載器的方式局部參與外, 其余動作都完全由Java虛擬機來主導控 制。 直到初始化階段, Java 虛擬機才真正開始執行類中編寫的Java程序代碼, 將主導權移交給應用程序。
初始化階段就是執行類構造器()方法的過程。 ()并不是程序員在Java代碼中直接編寫 的方法, 它是Javac編譯器的 自動生成物,()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊(static{}塊) 中的 語句合并產 生的, 編譯器收集的順序是由語句在源文件中出現的順序決定的, 靜態語句塊中只能訪問 到定義在靜態語句塊之 前的變量, 定義在它之后的變量, 在前面的靜態語句塊可以賦值, 但是不能訪 問。