1. 程式人生 > >【搞定Jvm面試】 面試官:談談 JVM 類載入過程是怎樣的?

【搞定Jvm面試】 面試官:談談 JVM 類載入過程是怎樣的?

類載入過程

Class 檔案需要載入到虛擬機器中之後才能執行和使用,那麼虛擬機器是如何載入這些 Class 檔案呢?

系統載入 Class 型別的檔案主要三步:載入->連線->初始化。連線過程又可分為三步:驗證->準備->解析。

載入

類載入過程的第一步,主要完成下面3件事情:

  1. 通過全類名獲取定義此類的二進位制位元組流
  2. 將位元組流所代表的靜態儲存結構轉換為方法區的執行時資料結構
  3. 在記憶體中生成一個代表該類的 Class 物件,作為方法區這些資料的訪問入口

虛擬機器規範多上面這3點並不具體,因此是非常靈活的。比如:"通過全類名獲取定義此類的二進位制位元組流" 並沒有指明具體從哪裡獲取、怎樣獲取。比如:比較常見的就是從 ZIP 包中讀取(日後出現的JAR、EAR、WAR格式的基礎)、其他檔案生成(典型應用就是JSP)等等。

一個非陣列類的載入階段(載入階段獲取類的二進位制位元組流的動作)是可控性最強的階段,這一步我們可以去完成還可以自定義類載入器去控制位元組流的獲取方式(重寫一個類載入器的 loadClass() 方法)。陣列型別不通過類載入器建立,它由 Java 虛擬機器直接建立。

類載入器、雙親委派模型也是非常重要的知識點,這部分內容會在後面的文章中單獨介紹到。

載入階段和連線階段的部分內容是交叉進行的,載入階段尚未結束,連線階段可能就已經開始了。

驗證

準備

準備階段是正式為類變數分配記憶體並設定類變數初始值的階段,這些記憶體都將在方法區中分配。對於該階段有以下幾點需要注意:

  1. 這時候進行記憶體分配的僅包括類變數(static),而不包括例項變數,例項變數會在物件例項化時隨著物件一塊分配在 Java 堆中。
  2. 這裡所設定的初始值"通常情況"下是資料型別預設的零值(如0、0L、null、false等),比如我們定義了public static int value=111 ,那麼 value 變數在準備階段的初始值就是 0 而不是111(初始化階段才會賦值)。特殊情況:比如給 value 變數加上了 fianl 關鍵字public static final int value=111 ,那麼準備階段 value 的值就被賦值為 111。

基本資料型別的零值:

解析

解析階段是虛擬機器將常量池內的符號引用替換為直接引用的過程。解析動作主要針對類或介面、欄位、類方法、介面方法、方法型別、方法控制代碼和呼叫限定符7類符號引用進行。

符號引用就是一組符號來描述目標,可以是任何字面量。直接引用就是直接指向目標的指標、相對偏移量或一個間接定位到目標的控制代碼。在程式實際執行時,只有符號引用是不夠的,舉個例子:在程式執行方法時,系統需要明確知道這個方法所在的位置。Java 虛擬機器為每個類都準備了一張方法表來存放類中所有的方法。當需要呼叫一個類的方法的時候,只要知道這個方法在方發表中的偏移量就可以直接呼叫該方法了。通過解析操作符號引用就可以直接轉變為目標方法在類中方法表的位置,從而使得方法可以被呼叫。

綜上,解析階段是虛擬機器將常量池內的符號引用替換為直接引用的過程,也就是得到類或者欄位、方法在記憶體中的指標或者偏移量。

初始化

初始化是類載入的最後一步,也是真正執行類中定義的 Java 程式程式碼(位元組碼),初始化階段是執行類構造器 <clinit> ()方法的過程。

對於<clinit>() 方法的呼叫,虛擬機器會自己確保其在多執行緒環境中的安全性。因為 <clinit>() 方法是帶鎖執行緒安全,所以在多執行緒環境下進行類初始化的話可能會引起死鎖,並且這種死鎖很難被發現。

對於初始化階段,虛擬機器嚴格規範了有且只有5種情況下,必須對類進行初始化:

  1. 當遇到 new 、 getstatic、putstatic或invokestatic 這4條直接碼指令時,比如 new 一個類,讀取一個靜態欄位(未被 final 修飾)、或呼叫一個類的靜態方法時。
  2. 使用 java.lang.reflect 包的方法對類進行反射呼叫時 ,如果類沒初始化,需要觸發其初始化。
  3. 初始化一個類,如果其父類還未初始化,則先觸發該父類的初始化。
  4. 當虛擬機器啟動時,使用者需要定義一個要執行的主類 (包含 main 方法的那個類),虛擬機器會先初始化這個類。
  5. 當使用 JDK1.7 的動態動態語言時,如果一個 MethodHandle 例項的最後解析結構為 REF_getStatic、REF_putStatic、REF_invokeStatic、的方法控制代碼,並且這個控制代碼沒有初始化,則需要先觸發器初始化。

參考

  • 《深入理解Java虛擬機器》
  • 《實戰Java虛擬機器》
  • https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html