首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

JVM 類別載入機制深度解析:《深入理解 Java 虛擬機》核心筆記

     編輯:Java 綜合教學

類別載入全生命週期

Java 虛擬機將 Class 檔案載入記憶體,經驗證、轉換解析、初始化,形成可直接使用的 Java 型別。Java 動態擴展特性仰賴執行期動態載入與動態連結。類別從載入到卸載,生命週期包含七階段:

  1. 載入
  2. 驗證
  3. 準備
  4. 解析
  5. 初始化
  6. 使用
  7. 卸載

其中驗證、準備、解析統稱「連接」。前五階段啟動順序固定,但執行常交叉混合(如解析在某些情況延至初始化後,支援動態繫結)。

類別初始化觸發條件(主動引用)

規範嚴格規定僅五種情況立即觸發初始化:

  1. 遇到 new、getstatic、putstatic、invokestatic 位元組碼指令,且類別未初始化。
  2. 使用 java.lang.reflect 反射呼叫類別。
  3. 初始化子類別時發現父類別未初始化,先觸發父類別初始化。
  4. JVM 啟動指定主類別,優先初始化該主類別。
  5. JDK 1.7 動態語言支援:java.lang.invoke.MethodHandle 解析結果為 ref_getstatic 等方法句柄,且對應類別未初始化。

除此之外皆為被動引用,不觸發初始化:

  • 子類別引用父類別靜態欄位,僅觸發父類別初始化。
  • 陣列定義引用類別(如 new SuperClass[10])。
  • 常量編譯期存入呼叫類別常量池,未直接引用定義類別。

介面初始化差異

介面亦有初始化過程(編譯器產生 <clinit>()),但不要求父介面全部初始化,僅在真正使用父介面成員時才初始化。類別初始化則要求父類別全部先完成。

類別載入詳細階段

載入階段

完成三件事:

  1. 透過全限定名獲取定義類別的二進制位元組流。
  2. 將位元組流靜態儲存結構轉換為方法區執行期資料結構。
  3. 在 Java 堆產生 java.lang.Class 物件,作為方法區資料存取入口。

非陣列類別載入可由開發者控制(自訂類別載入器);陣列類別由 JVM 直接建立,元素型別最終仍靠類別載入器。

驗證階段

確保 Class 檔案位元組流符合 JVM 要求、不危害安全。四階段:

  1. 檔案格式驗證:魔數 0xCAFEBABE、版本號、常量池支援度等,保證能正確解析存入方法區。
  2. 元資料驗證:語義分析,確認符合 Java 語言規範(如是否有父類別、是否繼承 final 類別、抽象類別是否實作所有抽象方法等)。
  3. 位元組碼驗證:資料流與控制流分析,確保程式語義合法邏輯。為提升效能,JDK 6 引入 StackMapTable 屬性描述基本區塊狀態,避免重複推導。
  4. 符號引用驗證:解析階段將符號引用轉為直接引用時執行,確保解析動作正常。

準備階段

正式為類別變數(static 欄位)配置記憶體並設初始值(通常為型別零值)。若欄位屬性表存在 ConstantValue 屬性(final static),則依指定值初始化。實例變數隨物件實例化在堆中配置。

解析階段

將常量池內符號引用替換為直接引用。符號引用與記憶體佈局無關,同一符號引用多次解析常見(除 invokedynamic),JVM 可快取首次結果。invokedynamic 服務動態語言,不快取。

四類引用解析:類/介面、欄位、類方法、介面方法。

初始化階段

執行類別建構器 <clinit>() 方法(編譯器自動收集所有類別變數賦值與 static 區塊語句,按原始碼順序合成)。關鍵細節:

  1. <clinit>() 收集順序依原始碼順序;static 區塊僅能存取定義在其之前的變數(可賦值不可讀取)。
  2. 不需顯式呼叫父類建構器,JVM 保證子類 <clinit>() 執行前父類已完成。
  3. 父類 static 區塊優於子類變數賦值執行。
  4. 無 static 區塊且無變數賦值則不產生 <clinit>()。
  5. 介面 <clinit>() 不需先執行父介面同方法。

類別載入器與雙親委派模型

任意類別由「載入它的類別載入器」+「類別本身」共同確立唯一性。比較兩類別是否相等,前提為同一載入器載入。

三大內建載入器

  • Bootstrap ClassLoader:啟動類別載入器(C++ 實作),載入 %JAVA_HOME%\lib 或 -Xbootclasspath 指定路徑中被 JVM 識別的類別庫(如 rt.jar),不可被 Java 程式直接引用。
  • Extension ClassLoader:擴充類別載入器(sun.misc.Launcher$ExtClassLoader),載入 %JAVA_HOME%\lib\ext 或 java.ext.dirs 指定路徑類別庫,開發者可直接使用。
  • Application ClassLoader:應用程式類別載入器(sun.misc.Launcher$AppClassLoader),載入使用者類別路徑(classpath)類別庫,為 ClassLoader.getSystemClassLoader() 回傳值,無自訂載入器時為預設載入器。

三者為父子關係,但採組合而非繼承實作。

雙親委派模型(JDK 1.2 推薦)

  1. 類別載入請求先委派父載入器。
  2. 層層向上至啟動類別載入器。
  3. 父載入器無法完成,子載入器嘗試;若最底層仍失敗,拋出 ClassNotFoundException。

優點:基礎類別由上層載入器載入,具備優先級層次,保障 Java 程式穩定運行。核心實作邏輯(loadClass):檢查已載入 → 父載入器委派 → 失敗則自身 findClass → 可選連結 → 回傳。

自訂類別載入器

繼承 java.lang.ClassLoader 並覆寫 findClass() 即可。ClassLoader 職責:依類別名稱尋找/產生位元組碼,定義為 java.lang.Class 實例;另負責載入資源(圖片、配置檔等)。相關方法:loadClass()、findClass()、defineClass()、getResource() 等。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved