1. 程式人生 > >JAVA 類載入過程詳細講解 -jvm載入類機制CLass Loading

JAVA 類載入過程詳細講解 -jvm載入類機制CLass Loading

jvm載入類機制CLass Loading

前提:java原始檔被javac編譯為class位元組碼檔案。
javac編譯時不進行連線(分配記憶體)工作,而是在jvm執行時才動態載入和動態連線

什麼是類的載入

jvm將class文讀取到記憶體中,經過對class檔案的校驗、轉換解析、初始化最終在jvm的heap和方法區分配記憶體形成可以被jvm直接使用的型別的過程。

類的生命週期

7個階段依次為:Loading Verification Preparation Resolution Initialization Using Unloading

載入 驗證 準備 初始化和解除安裝 的順序是確定的,而“解析”不一定在初始化之前,很有可能在初始化之後,實現java的偉大特性

執行時(晚期)繫結

一個階段的執行過程中會呼叫或啟用另一個階段

分階段解釋

1、載入Loading

這個階段jvm完成以下動作:
首先  類載入器通過類的全路徑限定名讀取類的二進位制位元組流,
然後  將二進位制位元組流代表的類結構轉化到執行時資料區的 方法區中,
最後  在jvm堆中生成代表這個類的java.lang.Class例項(不是這個類的例項)

類載入器

獲取類的二進位制流 既可以使用jvm自帶的類載入器,也可以自己寫載入器來載入,這一小步是完全可控的。不同的載入器可以從各種地方讀取:zip包jar包,class檔案,網路流 。。。讀取類的二進位制位元組流

同一個載入器載入的同源類才是真的同類。不同載入器載入同源類,不是同類!instanceof為FALSE

類載入的雙親委派模型

各個載入器都是先委託自己的父載入器載入類,若確實沒載入到再自己來載入

於是java預設的類查詢載入順序是自頂向下的,樹狀結構

雙親委託的意圖是保證java型別體系中最基礎的行為一致,優先載入JDK中的類

載入器主要有四種:

  • jvm啟動類載入器bootstrap loader,用c++實現為jvm的一部分(僅指sun的hotspot),負責 JAVA_HOME/lib下面的類庫中的類的載入,這個載入器,java程式無法引用到。

  • 擴充套件類載入器Extension Loader,由sun.misc.Launcher$ExtClassLoader類實現,可在java中使用,負責JAVA_HOME/lib/ext 目錄和java.ext.dir目錄中類庫的類的載入。

  • 應用系統類載入器Application System Loader,由sun.misc.Louncher$AppClassLoader實現,負責載入使用者類路徑中類庫中的類,如果沒有使用自定義的載入器,這個就是預設的 載入器!

  • 使用者自定義載入器 自己定義從哪裡載入類的二進位制流

OSGi的網狀載入模型

雙親委派是java設計者推薦的類載入器實現方式,可以在遵循的基礎上擴充套件,自定義類載入器的實現機制。

OSGi事實上的java模組化標準,他自定義的類載入器,能很多好實現模組化和模組的熱部署:更換一個bundle時,連同這個bundle的類載入器一同換掉。

OSGi中java.*開頭的類按照雙親載入機制載入,而其他類則都是由平級的類載入器載入的,形成一張網。 

2、驗證verification

Loading和 驗證是交叉進行的,驗證二進位制位元組流代表的位元組碼檔案是否合格,主要從一下幾方面判斷:

檔案格式:參看class檔案格式詳解,經過檔案格式驗證之後的位元組流才能進入方法區分配記憶體來儲存

元資料驗證:是否符合java語言規範

位元組碼驗證:資料流和控制流的分析,這一步最複雜

符號引用驗證:符號引用轉化為直接引用時(解析階段),檢測對類自身以外的資訊進行存在性、可訪問性驗證

如果確認程式碼安全無誤,可用 -Xverify:none關閉大部分類的驗證,加快類載入時間

3、準備preparation

在方法區中給類的類變數(static修飾)分配記憶體

然後初始化其值,如果類變數是常量,則直接賦值為該常量值否則為java型別的預設的零值。

4、解析resolution

指將常量池內的符號引用替換為直接引用的過程。未理解 再調研

5、初始化initialization

這個階段才真正開始執行java程式碼,靜態程式碼塊和設定變數的初始值為程式設計師設定的值

主動引用

有且只有下面5種情況才會立即初始化類,稱為主動引用:

  • new 物件時

  • 讀取或設定類的靜態欄位(除了 被final,已在編譯期把結果放入常量池的 靜態欄位)或呼叫類的靜態方法時;

  • 用java.lang.reflect包的方法對類進行反射呼叫沒初始化過的類時

  • 初始化一個類時發現其父類沒初始化,則要先初始化其父類

  • 含main方法的那個類,jvm啟動時,需要指定一個執行主類,jvm先初始化這個類

其他對類的引用 稱為被動引用,載入類時不會進行初始化動作

子類繼承父類時的初始化順序

   1.首先初始化父類的static變數和塊,按出現順序

   2.初始化子類的static變數和塊,按出現順序

   3.初始化父類的普通變數,呼叫父類的建構函式

   4.初始化子類的普通變數,呼叫子類的建構函式

類的初始化過程發生時刻: 

1. T是一個類,當T的一個例項建立的時候,也就是T t = new T(); 

2. T的一個靜態方法被呼叫的時候,也就是 T.staticField(); 

3. T的靜態屬性被賦值的時候,T.staticField = o; 

4. T的一個靜態屬性被使用的時候,也就是 Object o = T.staticField; 但是它不是常量。 

5. T is a top level class , and an assert statement  lexically nested