JVM學習筆記① 類的生命週期以及類加載機制

類的生命週期

image

這 7 個階段中的:加載、驗證、準備、初始化、卸載的順序是固定的。但它們並不一定是嚴格同步串行執行,它們之間可能會有交叉,但總是以“開始”的順序總是按部就班的。至於解析則有可能在初始化之後纔開始,這是爲了支持Java語言的運行時綁定(也稱爲動態綁定或晚期綁定)。

類的加載

類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類的加載的最終產品是位於堆區中的Class對象,Class對象封裝了類在方法區內的數據結構,並且向Java程序員提供了訪問方法區內的數據結構的接口。

類加載器並不需要等到某個類被“首次主動使用”時再加載它,JVM規範允許類加載器在預料某個類將要被使用時就預先加載它,如果在預先加載的過程中遇到了.class文件缺失或存在錯誤,類加載器必須在程序首次主動使用該類時才報告錯誤(LinkageError錯誤)如果這個類一直沒有被程序主動使用,那麼類加載器就不會報告錯誤。

其中類加載的過程包括了加載、驗證、準備、解析、初始化五個階段。在這五個階段中,加載、驗證、準備和初始化這四個階段發生的順序是確定的,而解析階段則不一定,它在某些情況下可以在初始化階段之後開始,這是爲了支持Java語言的運行時綁定(也成爲動態綁定或晚期綁定)。另外注意這裏的幾個階段是按順序開始,而不是按順序進行或完成,因爲這些階段通常都是互相交叉地混合進行的,通常在一個階段執行的過程中調用或激活另一個階段。

1. 加載

加載是類裝載的第一步,首先通過一個類的全限定名來獲取其定義的二進制字節流。讀取到二進制流,並解析二進制流將裏面的元數據(類型、常量等)載入到方法區,在java堆中生成對應的java.lang.Class對象。

相對於類加載的其他階段而言,加載階段(準確地說,是加載階段獲取類的二進制字節流的動作)是可控性最強的階段,因爲開發人員既可以使用系統提供的類加載器來完成加載,也可以自定義自己的類加載器來完成加載。

加載階段完成後,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,而且在Java堆中也創建一個java.lang.Class類的對象,這樣便可以通過該對象訪問方法區中的這些數據。

2. 連接
2.1 驗證

驗證是連接階段的第一步,這一階段的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。驗證階段大致會完成4個階段的檢驗動作:

文件格式驗證:驗證字節流是否符合Class文件格式的規範;例如:是否以0xCAFEBABE開頭、主次版本號是否在當前虛擬機的處理範圍之內、常量池中的常量是否有不被支持的類型。

元數據驗證:對字節碼描述的信息進行語義分析(注意:對比javac編譯階段的語義分析),以保證其描述的信息符合Java語言規範的要求;例如:這個類是否有父類,除了java.lang.Object之外。

字節碼驗證:通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的。

符號引用驗證:確保解析動作能正確執行。例如:常量池中描述類是否存在,訪問的方法或字段是否存在且有足夠的權限。

驗證階段是非常重要的,但不是必須的,它對程序運行期沒有影響,如果所引用的類經過反覆驗證,那麼可以考慮採用-Xverifynone參數來關閉大部分的類驗證措施,以縮短虛擬機類加載的時間。

2.2 準備

爲類的靜態變量(static)分配內存,並將其初始化爲默認值.

對基本數據類型來說,對於類變量(static)和全局變量,如果不顯式地對其賦值而直接使用,則系統會爲其賦予默認的零值(如int爲0),而對於局部變量來說,在使用前必須顯式地爲其賦值,否則編譯時不通過。

對於被final和static修飾的常量,那麼在準備階段就會被初始化爲指定的值。

2.3 解析

把類中的符號引用轉換爲直接引用。

符號引用就是一組符號來描述目標,可以是任何字面量。

直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。

例如某個類繼承java.lang.object,原來的符號引用記錄的是“java.lang.object”這個符號,憑藉這個符號並不能找到java.lang.object這個對象在哪裏,而直接引用就是要找到java.lang.object所在的內存地址,建立直接引用關係。

3. 初始化

JVM負責對類進行初始化,主要對類變量進行初始化,包括執行類構造方法、static變量賦值語句,staic{}語句塊,同時若該類有父類,則會先初始化父類。

類初始化時機:只有當對類的主動使用的時候纔會導致類的初始化,類的主動使用包括以下六種:

  1. 創建類的實例,也就是new的方式

  2. 訪問某個類或接口的靜態變量,或者對該靜態變量賦值

  3. 調用類的靜態方法

  4. 反射(如Class.forName(“com.shengsiyuan.Test”))

  5. 初始化某個類的子類,則其父類也會被初始化

  6. Java虛擬機啓動時被標明爲啓動類的類(Java Test),直接使用java.exe命令來運行某個主類

類加載器

類加載器ClassLoader,它是一個抽象類,ClassLoader的具體實例負責把java字節碼讀取到JVM當中,ClassLoader還可以定製以滿足不同字節碼流的加載方式,比如從網絡加載、從文件加載。ClassLoader的負責整個類裝載流程中的“加載”階段。

image

1. 啓動類加載器(Bootstrap ClassLoader)

此類由c++實現,該加載器負責將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,並且是虛擬機識別的(僅按照文件名識別,如 rt.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)類庫加載到虛擬機內存中。啓動類加載器無法被Java程序直接引用,用戶在編寫自定義類加載器時,如果需要把加載請求委派給啓動類加載器,直接使用 null 代替即可。

2. 擴展類加載器(Extension ClassLoader)

這個類加載器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實現的。它負責將 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系統變量所指定路徑中的所有類庫加載到內存中,開發者可以直接使用擴展類加載器。

3. 應用程序類加載器(Application ClassLoader)

這個類加載器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)實現的。由於這個類加載器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般稱爲系統類加載器。它負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這個類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。

4. 自定義類加載器

自定義類加載器步驟:

  • 定義一個類,繼承 ClassLoader
  • 重寫 loadClass 方法
  • 實例化 Class 對象

類加載器是 Java 語言的一項創新,也是 Java 語言流行的重要原因之一,它最初的設計是爲了滿足 java applet 的需求而開發出來的
高度的靈活性,通過自定義類加載器可以實現熱部署、代碼加密等。

雙親委派模式

如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把請求委託給父加載器去完成,依次向上,因此,所有的類加載請求最終都應該被傳遞到頂層的啓動類加載器中,只有當父加載器在它的搜索範圍中沒有找到所需的類時,即無法完成該加載,子加載器纔會嘗試自己去加載該類。

意義:

Java類和它的類加載器一起具備了一種帶有優先級的層次關係,不會被重複加載,保證了java程序的穩定性。

同時這樣可以使得java核心api中定義類型不會被隨意替換,保證了安全性。

參考:

https://jybeyonding.github.io/2018/03/16/深入理解Java虛擬機-第7章-虛擬機類加載機制/

https://www.cnblogs.com/ityouknow/p/5603287.html

https://www.cnblogs.com/leefreeman/p/7429112.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章