閱讀479 返回首頁    go 阿裏雲 go 技術社區[雲棲]


JVM學習筆記(三)——虛擬機類加載機製

在介紹完class文件格式後,我們來看下虛擬機是如何把一個由class文件描述的類加載到內存中的。具體來說java中類的加載涉及7個階段:加載、校驗、準備、解析、初始化、使用、卸載。

1.加載時機

並不是所有的類在程序啟動時即被加載,為提升效率,虛擬機通常秉承的是按需加載的原則,即需要使用到相應的類時才加載對應的類。具體包括如下幾個加載時機:

  • 遇到new、getstatic、putstatic、invokestatic這4條指令時,如果對應的類沒有被加載,虛擬機會首先加載對應的類。這4條指令對應的場景是:
    • 創建一個實例對象
    • 訪問一個類的靜態變量(注意:不包括被final修飾,在編譯時已被放入常量池的變量)
    • 執行一個類的靜態方法
  • 使用java.lang.reflect包的方法對類進行反射調用時,如果相應類未被加載,則虛擬機會加載該類
  • 初始化子類時如果其父類尚未被加載,虛擬機會先加載其父類
  • 虛擬機啟動時,包含main方法的類會被加載
  • 使用JDK 1.7動態語言支持時,某些場景會觸發類加載
  1. 加載

加載是整個類加載的一個過程,具體來說加載階段一共做了三項工作:

  • 通過一個類的全限定名來獲取定義此類的二進製字節流
  • 將字節流中的靜態存儲結構轉化為運行中的實際數據結構並存儲在方法區中
  • 為該類生成一個java.lang.Class對象,作為方法區這個類的各種數據的訪問入口

3.驗證

驗證階段的目的就是保證Class文件的字節流中包含的信息都符合當前虛擬機的要求,不會危害虛擬機本身的安全。具體來說驗證階段的工作主要分為以下幾部分:

3.1 文件格式驗證

  • 是否已0xCAFEBABE開頭
  • 主次版本是否在當前虛擬機可以處理的範圍內
  • 常量池中的數據是否有不被支持的類型
  • 指向常量的各種索引值是否有指向不存在常量或不符要求的常量
  • …...

3.2 元數據驗證

  • 當前加載類是否有父類
  • 是否繼承了不被允許繼承的類(final類)
  • 如果不是抽象類,是否實現了父類中所有要求實現的方法
  • …...

3.3 字節碼驗證

字節碼驗證是整個驗證過程中最為複雜的一步,主要的目的是通過分析數據流和控製流,確定語義是合法的、符合邏輯的,例如:

  • 保證跳轉指令不會跳轉到方法體以外的字節碼指令上
  • 保證方法體內的類型轉換是有效的
  • …...

3.4 符號引用驗證

  • 符號引用中通過字符串描述的全限定名是否能夠找到對應的類
  • 符號引用中的類、字段、方法的訪問性(private、protected、public、default)是否可被當前的類訪問

4 準備

正式為類變量分配內存並設置其初始值,這些變量所使用的內存都將在方法區進行分配。

5 解析

解析是虛擬機將class文件中常量池中的符號引用解析為直接應用的過程。

  • 符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任意形式的字麵量,隻要使用時能無歧義的定位到目標即可,與虛擬機的內存布局無關。
  • 直接引用:直接引用可以直接訪問到存在於內存中的目標,可以是一個直接指針也可以是一個句柄。

解析過程主要涉及以下幾個步驟:

  • 類或接口的解析
  • 字段解析
  • 類方法解析
  • 接口方法解析
  • 方法類型解析
  • 方法句柄解析
  • 調用點限定符解析

6 初始化

初始化就是執行類構造器方法()的過程,()方法是由編譯器自動收集的所有類變量的賦值動作以及靜態語塊合並生成的。

7 類加載器

上述的類加載過程都是由java虛擬機的類加載器完成的。對於任意一個類,都需要有加載它的類加載器和這個類本身一同確立其在java虛擬機中的唯一性,每一個類加載器都擁有一個獨立的類命名空間。事實上Java程序在運行時存在不止一種類加載器,絕大部分Java程序都會使用到以下三種類加載器:

  • 啟動類加載器:用於加載/lib路徑下的類
  • 擴展加載器:用於加載/lib/ext路徑下的類
  • 應用程序類加載器:複雜加載用戶應用程序路徑上的類

如果有需要,開發人員還可以加入自定義的類加載器。既然存在如此多的類加載器,那麼當一個類需要加載時,具體是由那個類進行加載呢?由於所有的類加載器都遵守“雙親委派模型”,所以虛擬機在運行期間可以保證一個類隻會被加載一次。
這裏寫圖片描述

雙親委派模型的工作過程:如果一個類加載器收到了類加載的請求,它會把這個請求交給自己的父類加載器去完成,父類加載器也會繼續上自己的父類加載器發送請求,依次類推。如果父類已經加載過該類,則當前加載器會直接返回已加載的類,隻有當父類沒有加載過該類時,當前類加載器才會真正去加載該類。

最後更新:2017-07-11 01:02:48

  上一篇:go  JVM學習筆記(四)——字節碼執行引擎
  下一篇:go  JVM學習筆記(二)——Class文件結構