jvm的組成部分詳解——————類加載機制!

類加載過程:https://blog.csdn.net/acm_lkl/article/details/79188967原文鏈接 

以下是自己的總結:

類加載的整個生命週期包含以下幾個:加載、驗證、準備、解析、初始化、使用、卸載

但是可以確定的加載過程只有以下幾個:加載、驗證、解析、初始化、卸載

知道整個加載過程以後我們要知道什麼時候會加載類:有5種情況會被加載

1.遇到new、getstatic、putstatic或invokestatic這四條字節碼指令時,如果類沒有進行初始化,則需要先觸發其初始化。生成這四條指令的場景Java代碼場景是:使用new關鍵詞實例化類的對象、讀取或設置一個類的靜態字段(被final修飾、已在編譯期把結果放入常量池的靜態字段除外)、調用一個類的靜態方法的時候。 

2.使用java.lang.reflect包的方法對類進行反射調用的時候,如果類還沒有進行過初始化,則需要先進行初始化。 
3)當初始化一個類的時候,如果發現其父類還沒有初始化,則需要先初始化其父類。 
4)當虛擬機啓動時,用戶需要制定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類 
5) 當使用JDK1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,並且這個方法句柄對應的類沒有進行過初始化,則需要先觸發其初始化。 
以上5中情況被稱爲一個類的主動引用

 

一.加載階段

加載”是“類加載”(Class Loading)過程的一個階段,希望讀者沒有混淆這兩個看起來很相似的名詞。在加載階段,虛擬機需要完成以下3件事情:
1)通過一個類的全限定名來獲取定義此類的二進制字節流。
2)將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構。
3)在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口。

 

 

二。驗證階段:

 主要就是爲了保證加載的Class文件是否是安全的,符合jvm規範的,不符合就會輸出“虛擬機就應拋出一個java.lang.VerifyError異常或其子類異常”

這裏根據《深入瞭解jvm》有詳細的解析,不想再複述!

三。準備階段:

這個階段就是分配內存的階段,比如類變量的初始值設置,分配內存,他們都是保存在方法區內存,

這個階段中有兩個容易產生混淆的概念需要強調一下,首先,這時候進行內存分配的僅包括類變量(被static修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨着對象一起分配在Java堆中。其次,這裏所說的初始值“通常情況”下是數據類型的零值,假設一個類變量的定義爲:

public static int value=123;

那變量value在準備階段過後的初始值爲0而不是123,因爲這時候尚未開始執行任何Java方法,而把value賦值爲123的putstatic指令是程序被編譯後,

四。解析階段:

主要做的事情就是把jvm中常量池的符號引用改爲直接引用!

符號引用:

符號引用(Symbolic References):符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機實現的內存佈局無關,引用的目標並不一定已經加載到內存中。各種虛擬機實現的內存佈局可以各不相同,但是它們能接受的符號引用必須都是一致的,因爲符號引用的字面量形式明確定義在Java虛擬機規範的Class文件格式中。(如果這部分不太理解建議百度,不想誤導別人,引用了《深入瞭解JVM》)

直接引用(Direct References):直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是和虛擬機實現直接引用(Direct References):直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是和虛擬機實現。

*:對於一個符號引用,虛擬機會在第一次加載之後就對其進行緩存,並且如果成功加載一個符號引用之後後續再次加載相同的符號引用是不會再次進行解析了。對於加載成功與否,如果第一次加載失敗了,那麼後續所有對該符號引用的使用都會加載失敗,如果第一次加載成功了,那麼後續所有對其再次加載也都會成功。

五.初始化:
初始化是類加載過程的最後一步,前面的類加載過程中,除了在加載階段用戶應用程序通過自定義類加載器參與之外,其餘動作完全由虛擬機主導。到了初始化過程,纔會真正開始執行類中定義的Java程序代碼。這一步主要就是執行靜態變量的初始化,包括靜態變量的賦值和靜態初始化塊的執行。這裏需要引入一個類構造器<clinit>()方法,下面對其進行簡單的介紹:

<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態初始化塊中的語句合併產生的,編譯器收集的順序是由語句在源文件中出現的順序決定的,靜態初始化塊中只能訪問到定義在靜態語句塊之前的變量,定義在它之後的變量靜態語句塊中可以賦值但是不能訪問(這裏有一點需要注意,靜態初始化塊和靜態變量賦值語句執行順序是按定義順序來的,並不是說初始化塊一定會在靜態賦值語句之後執行)。
<clinit>()方法與類的構造函數不同,它不需要顯示的調用父類的<clinit>()方法,虛擬機會保證在子類的<clinit>()方法執行之前,父類的<clinit>()方法已經執行完畢。因此在虛擬機中第一個被執行的<clinit>()方法的類肯定是java.lang.Object。
<clinit>()方法對於類或接口來說不是必需的,如果一個類中沒有靜態語句塊,也沒有對變量的賦值操作,那麼編譯器可以不爲這個類生成<clinit>()方法。
接口中不能使用靜態初始化塊,但是仍有static變量的賦值操作,所以也會有<clinit>()方法,但是接口執行<clinit>()方法不需要先執行父接口的<clinit>()方法。只有當父接口中定義的變量被使用到時,纔會執行<clinit>()方法。
虛擬機會保證一個類的<clinit>()方法在多線程環境中被正確的加鎖、同步,如果多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的<clinit>()方法,其它線程都需要阻塞等待。
 

 

關於jvm的類加載器:建議願意花時間的同學去買一本《自己動手寫jvm》的書!現在有多個開源版本

 

本文引用了《深入瞭解jvm》的一些文段!

 

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