JVM:类加载机制之类加载过程
?
類加載機制概念
?Java虛擬機把描述類的數據從Class文件加載到內存,并對數據進行校驗、準備、解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的加載機制。*
Class文件由類裝載器裝載后,在JVM中將形成一份描述Class結構的元信息對象,通過該元信息對象可以獲知Class的結構信息:如構造函數,屬性和方法等,Java允許用戶借由這個Class相關的元信息對象間接調用Class對象的功能,這里就是我們經常能見到的Class類。
類從被加載到虛擬機內存中開始,到卸載出內存為止,它的整個生命周期包括了:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(using)、和卸載(Unloading)七個階段。其中驗證、準備和解析三個部分統稱為連接(Linking),這七個階段的發生順序如下圖所示:?
工作機制
類裝載器就是尋找類的字節碼文件,并構造出類在JVM內部表示的對象組件。在Java中,類裝載器把一個類裝入JVM中,要經過以下步驟:
?(1) 裝載:查找和導入Class文件; (2) 鏈接:把類的二進制數據合并到JRE中;(a)校驗:檢查載入Class文件數據的正確性;(b)準備:給類的靜態變量分配存儲空間;(c)解析:將符號引用轉成直接引用;(3) 初始化:對類的靜態變量,靜態代碼塊執行初始化操作1. 裝載(加載) (重點)
什么是類的裝載
類的裝載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然后在堆區創建一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類的加載的最終產品是位于堆區中的Class對象,Class對象封裝了類在方法區內的數據結構,并且向Java程序員提供了訪問方法區內的數據結構的接口。?
?
類加載器并不需要等到某個類被“首次主動使用”時再加載它,JVM規范允許類加載器在預料某個類將要被使用時就預先加載它,如果在預先加載的過程中遇到了.class文件缺失或存在錯誤,類加載器必須在程序首次主動使用該類時才報告錯誤(LinkageError錯誤)如果這個類一直沒有被程序主動使用,那么類加載器就不會報告錯誤。
加載.class文件的方式有:
1.從本地系統中直接加載
2.通過網絡下載.class文件
3.從zip,jar等歸檔文件中加載.class文件
4.從專有數據庫中提取.class文件
5. 將Java源文件動態編譯為.class文件
在了解了什么是類的加載后,回頭來再看jvm進行類加載階段都做了什么。虛擬機需要完成以下三件事情:
1.通過一個類的全限定名稱來獲取定義此類的二進制字節流。 2.將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。 3.在java堆中生成一個代表這個類的java.lang.Class對象,作為方法區這些數據的訪問入口。相對于類加載過程的其他階段,加載階段是開發期相對來說可控性比較強,該階段既可以使用系統提供的類加載器完成,也可以由用戶自定義的類加載器來完成,開發人員可以通過定義自己的類加載器去控制字節流的獲取方式。關于這個過程的更多細節,我會在下一節細說,類的加載。?
加載階段完成后,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,而且在Java堆中也創建一個java.lang.Class類的對象,這樣便可以通過該對象訪問方法區中的這些數據。
2. 驗證
驗證的目的是為了確保Class文件中的字節流包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。不同的虛擬機對類驗證的實現可能會有所不同,但大致都會完成以下四個階段的驗證:文件格式的驗證、元數據的驗證、字節碼驗證和符號引用驗證。
?1)文件格式的驗證:驗證字節流是否符合Class文件格式的規范,并且能被當前版本的虛擬機處理,該驗證的主要目的是保證輸入的字節流能正確地解析并存儲于方法區之內。經過該階段的驗證后,字節流才會進入內存的方法區中進行存儲,后面的三個驗證都是基于方法區的存儲結構進行的。 ?2)元數據驗證:對類的元數據信息進行語義校驗(其實就是對類中的各數據類型進行語法校驗),保證不存在不符合Java語法規范的元數據信息。 ?3)字節碼驗證:該階段驗證的主要工作是進行數據流和控制流分析,對類的方法體進行校驗分析,以保證被校驗的類的方法在運行時不會做出危害虛擬機安全的行為。 4 )符號引用驗證:這是最后一個階段的驗證,它發生在虛擬機將符號引用轉化為直接引用的時候(解析階段中發生該轉化,后面會有講解),主要是對類自身以外的信息(常量池中的各種符號引用)進行匹配性的校驗。學習鏈接的驗證這一步,需要了解class文件的結構!
其實文件格式的驗證、元數據的驗證、字節碼的驗證、符號引用的驗證,其實都是保證class文件格式的正確性!
? ? ? ? ? ? ? ? ? ? ?
3. 準備
準備階段是正式為類變量分配內存并設置類變量初始值的階段,這些內存都將在方法區中進行分配。?
注:(方法區)
1)這時候進行內存分配的僅包括類變量(靜態變量static),而不包括實例變量,實例變量會在對象實例化時隨著對象一塊分配在Java堆中,例如上圖,這一階段,v只會被初始化為0,而不會賦值給1!賦值給1是在初始化階段才會按照用戶程序的設定值進行初始化!但是,對于static final 變量會直接設值初始化!
2)這里所設置的初始值通常情況下是數據類型默認的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。
4. 解析
解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。
比如說你的父類是com.imooc.src.Pojo,那么這就是符號引用,說白了就是一個字符串!但是我們在運行的時候需要的是內存的地址!因此此階段就是將這些字符串引用轉化為直接引用(也就是這個字符串引用在實際內存中的地址)
符號引用(Symbolic Reference):符號引用以一組符號來描述所引用的目標,符號引用可以是任何形式的字面量,符號引用與虛擬機實現的內存布局無關,引用的目標并不一定已經在內存中。 直接引用(Direct Reference):直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是與虛擬機實現的內存布局相關的,同一個符號引用在不同的虛擬機實例上翻譯出來的直接引用一般都不相同,如果有了直接引用,那引用的目標必定已經在內存中存在。 1、類或接口的解析:判斷所要轉化成的直接引用是對數組類型,還是普通的對象類型的引用,從而進行不同的解析。 2、字段解析:對字段進行解析時,會先在本類中查找是否包含有簡單名稱和字段描述符都與目標相匹配的字段,如果有,則查找結束;如果沒有,則會按照繼承關系從上往下遞歸搜索該類所實現的各個接口和它們的父接口,還沒有,則按照繼承關系從上往下遞歸搜索其父類,直至查找結束。 3、類方法解析:對類方法的解析與對字段解析的搜索步驟差不多,只是多了判斷該方法所處的是類還是接口的步驟,而且對類方法的匹配搜索,是先搜索父類,再搜索接口。 4、接口方法解析:與類方法解析步驟類似,只是接口不會有父類,因此,只遞歸向上搜索父接口就行了。5. 初始化
類初始化階段是類加載過程的最后一步,前面的類加載過程中,除了加載(Loading)階段用戶應用程序可以通過自定義類加載器參與之外,其余動作完全由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的Java程序代碼。?
初始化,為類的static靜態變量賦予正確的初始值,JVM負責對類進行初始化,主要對類變量進行初始化(靜態變量static)。在Java中對類變量進行初始值設定有兩種方式:
JVM初始化步驟
1、假如這個類還沒有被加載和連接,則程序先加載并連接該類 2、假如該類的直接父類還沒有被初始化,則先初始化其直接父類 3、假如類中有初始化語句,則系統依次執行這些初始化語句初始化階段是執行類構造器()方法的過程。
類初始化的觸發條件:只有當對類的主動使用的時候才會導致類的初始化。
?(1) 創建類的實例,也就是new的方式
(2) 訪問某個類或接口的靜態變量,或者對該靜態變量賦值
(3) 調用類的靜態方法
(4) 反射(如Class.forName(“com.shengsiyuan.Test”))
(5) 初始化某個類的子類,則其父類也會被初始化
(6) Java虛擬機啟動時被標明為啟動類的類(Java Test),直接使用java.exe命令來運行某個主類
除此以外,所有其他方式都不會觸發初始化,稱為被動引用。?
?
結束生命周期
在以下情況的時候,Java虛擬機會結束生命周期?
1. 執行了System.exit()方法?
2. 程序正常執行結束?
3. 程序在執行過程中遇到了異常或錯誤而異常終止?
4. 由于操作系統出現錯誤而導致Java虛擬機進程終止
總結
以上是生活随笔為你收集整理的JVM:类加载机制之类加载过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM:类加载机制之类加载器
- 下一篇: ubuntu下最简单的MySQL安装教程