JVM類加載思惟導圖

用一張思惟導圖儘量囊括一下JVM的類加載過程的全流程。java

本文參考自來自周志明《深刻理解Java虛擬機(第2版)》,拓展內容建議讀者能夠閱讀下這本書。

class loading

文字版以下:數組

加載 Loading

過程

  • 經過類的全限定名來獲取定義此類的二進制字節流數據結構

    • 非數組類的加載,由類加載器加載,能夠是啓動類加載器,也能夠是用戶自定義的類加載器
    • 數組類的加載,不禁類加載器建立,而是由JVM直接在內部建立多線程

      • 組件類型(數組降一維後的類型)是引用類型,遞歸調用加載過程直到降到一維類型後經過類加載器加載,數組類型最終標識爲此類加載器所加載,數組類可見性和組件類型保持一致
      • 組件類型不是引用類型而是原始類型,則該數組類型的類加載器將標識爲啓動類加載器,數組類型可見性爲public
  • 將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構
  • 在內存中(HotSpot爲方法區)生成一個表明了這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口

類加載器

  • 啓動類加載器 Bootstrap ClassLoader,加載<JAVA_HOME>/lib中的類
  • 拓展類加載器 Extension ClassLoader,加載<JAVA_HOME>/lib/ext中的類
  • 應用程序類加載器 Application ClassLoader,加載用戶類路徑上的ClassPath中的類
  • 自定義類加載器 User ClassLoader

鏈接 Linking

驗證 Verification

  • 文件格式驗證:字節流是否符合Class文件格式規範佈局

    • 是否以magic開頭
    • 主次版本號是否在虛擬機處理範圍內
    • 常量池中的常量是否有不支持的類型
    • 指向的常量索引值是否有指向不存在常量或不符合類型常量的狀況
    • CONSTANT_Utf8_info的常量是否符合utf8編碼規範
    • Class文件各個部分及文件自己是否有被刪除或附加的其餘信息
  • 元數據驗證:字節碼描述的信息進行語義分析編碼

    • 是否有父類
    • 父類是否繼承了不容許被繼承的類(final的)
    • 若是不是抽象類是否實現了其父類或接口之中要求實現的類
    • 類中字段、方法是否與父類產生矛盾spa

      • 覆蓋了父類的final字段
      • 不符合規範的方法重載
方法參數類型一致返回值類型不一致
  • 字節碼驗證:經過數據流和控制流分析程序語義的合法性,即類的方法體的校驗分析線程

    • 保證時刻操做數棧與指令代碼序列能配合工做
    • 保證跳轉指令不會跳轉到方法體之外的字節碼指令上
    • 保證方法體的類型轉換是有效的
  • 符號引用驗證:類的常量池中各類符號引用的信息進行匹配性校驗指針

    • 符號引用中經過字符串描述的全限定名是否能找到對應類
    • 指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段
    • 符號引用中的類、字段和方法的訪問性是否可被當前類所訪問

鏈接 Linking

驗證 Verification

  • 文件格式驗證:字節流是否符合Class文件格式規範對象

    • 是否以magic開頭
    • 主次版本號是否在虛擬機處理範圍內
    • 常量池中的常量是否有不支持的類型
    • 指向的常量索引值是否有指向不存在常量或不符合類型常量的狀況
    • CONSTANT_Utf8_info的常量是否符合utf8編碼規範
    • Class文件各個部分及文件自己是否有被刪除或附加的其餘信息
  • 元數據驗證:字節碼描述的信息進行語義分析

    • 是否有父類
    • 父類是否繼承了不容許被繼承的類(final的)
    • 若是不是抽象類是否實現了其父類或接口之中要求實現的類
    • 類中字段、方法是否與父類產生矛盾

      • 覆蓋了父類的final字段
      • 不符合規範的方法重載
方法參數類型一致返回值類型不一致
  • 字節碼驗證:經過數據流和控制流分析程序語義的合法性,即類的方法體的校驗分析

    • 保證時刻操做數棧與指令代碼序列能配合工做
    • 保證跳轉指令不會跳轉到方法體之外的字節碼指令上
    • 保證方法體的類型轉換是有效的
  • 符號引用驗證:類的常量池中各類符號引用的信息進行匹配性校驗

    • 符號引用中經過字符串描述的全限定名是否能找到對應類
    • 指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段
    • 符號引用中的類、字段和方法的訪問性是否可被當前類所訪問

準備 Preparation

  • 類變量分配空間
  • 類變量分配零值
  • 類常量分配初始值:由類字段的ConstantValue屬性進行賦值

解析 Resolution

  • 實際上就是把常量池中的符號引用替換爲直接引用的過程

    • 符號引用

      • 在常量池中即非字面量的類型

        • CONSTANT_Class_info
        • CONSTANT_Fieldref_info
        • CONSTANT_Methodref_info
        • CONSTANT_InterfaceMethodref_info
      • 特徵

        • 與虛擬機實現的內存佈局無關
        • 引用的目標並不必定已經加載到內存中
        • 由虛擬機Class文件格式規範,所以不一樣虛擬機可以接受的符號引用格式是肯定的
    • 直接引用

      • 表達形式

        • 直接指向目標的虛擬機內存中的指針
        • 相對偏移量
        • 可以定位到目標的句柄
      • 特徵

        • 與虛擬機的內存佈局直接相關
        • 引用的目標必須已經存在於內存中
        • 同一符號引用在不一樣虛擬機中的直接引用通常不一樣,由虛擬機本身制定格式
  • 符號引用解析

    • 類或接口的解析

      • 對CONSTANT_Class_info符號引用的解析

        • 對全限定名的解析
      • 虛擬機加載類D中的類符號引用N爲一個類或接口C的直接引用

        • C不是數組類型

          • 虛擬機將N表明的全限定名傳遞給D的類加載器來加載C
          • C被成功加載後(多是以前已經加載過或者本次執行了首次加載),虛擬機將D中的符號引用N替換爲C的直接引用
          • 符號引用驗證,如D是否具有對C的訪問權限
        • C是數組類型


          • 虛擬機將N表明的全限定名(如[Ljava.lang.Integer)傳遞給D的類加載器來加載C(詳見數組類加載流程)

            • D的類加載器先加載C的組件類型(如java.lang.Integer)
            • 虛擬機在方法區生成一個表明了數組維度和組件類型的數組對象
          • C被成功加載後,虛擬機將D中的符號引用N替換爲C的直接引用
          • 符號引用驗證,如D是否具有對C的訪問權限
    • 字段解析

      • 對CONSTANT_Fieldref_info符號引用的解析

        • 對class_index的解析
        • 對nameAndType_index的解析
      • 虛擬機在類D中加載字段符號引用N爲字段F的直接引用

        • 虛擬機在N中指定的類C裏尋找字段描述符和N中指定的字段描述符一致的字段F

          • 能找到,就將符號引用N替換爲F的直接引用

            • 符號引用驗證,如D是否具有對F的訪問權限
          • 找不到,在類C實現的接口中按照繼承關係從下向上尋找字段描述符和N中指定的字段描述符一致的字段F

            • 能找到就將符號引用N替換爲F的直接引用

              • 符號引用驗證,如D是否具有對F的訪問權限
            • 找不到,在類C繼承的父類中按照繼承關係從下向上尋找字段描述符和N中指定的字段描述符一致的字段F

              • 能找到就將符號引用N替換爲F的直接引用

                • 符號引用驗證,如D是否具有對F的訪問權限
              • 找不到,拋出java.lang.NoSuchFieldError異常
    • 類方法解析

      • 對CONSTANT_Methodref_info符號引用的解析

        • 對class_index的解析
        • 對nameAndType_index的解析
      • 虛擬機在類D中加載類方法符號引用N爲方法M的直接引用

        • 虛擬機在N中指定的類C裏尋找方法描述符和N中指定的方法描述符一致的方法M

          • 能找到

            • class_index指定的類不是接口,就將符號引用N替換爲M的直接引用
            • class_index指定的類是接口,拋出java.lang.IncompatibleClassChangeError異常
            • 符號引用驗證,如D是否具有對M的訪問權限
          • 找不到,在類C繼承的父類中按照繼承關係從下向上尋找方法描述符和N中指定的方法描述符一致的方法M

            • 能找到就將符號引用N替換爲M的直接引用

              • 符號引用驗證,如D是否具有對M的訪問權限
            • 找不到,在類C實現的接口中按照繼承關係從下向上尋找方法描述符和N中指定的方法描述符一致的方法M

              • 能找到,說明類C是抽象類,拋出java.lang.AbstractMethodError異常(爲何說明C是抽象類呢?C的方法在C中找不到,可是在C實現的接口中找到了,這意味着C實現了接口可是沒有實現接口的這個方法,所以C類只多是抽象類。)
              • 找不到,拋出java.lang.NoSuchMethodError異常
    • 接口方法解析

      • 對CONSTANT_InterfaceMethodref_info符號引用的解析

        • 對class_index的解析
        • 對nameAndType_index的解析
      • 虛擬機在類D中加載接口方法符號引用N爲方法M的直接引用

        • 虛擬機在N中指定的接口C裏尋找方法描述符和N中指定的方法描述符一致的方法M

          • 能找到

            • class_index指定的類是C接口,就將符號引用N替換爲M的直接引用
            • class_index指定的類C不是接口,拋出java.lang.IncompatibleClassChangeError異常
            • 符號引用驗證,接口方法都是public的因此沒有訪問權限的問題
          • 找不到,在接口C繼承的父接口中按照繼承關係從下向上尋找方法描述符和N中指定的方法描述符一致的方法M

            • 能找到就將符號引用N替換爲M的直接引用
            • 找不到,拋出java.lang.NoSuchMethodError異常

初始化 Initialization

初始化就是執行<clinit>()方法的過程

<clinit>()

  • <clinit>()是編譯期生成在Class字節碼中的,由編譯器自動收集類中的全部類變量的賦值動做和靜態代碼塊static{…}中的語句合併而成
  • <clinit>()是類構造器,與實例構造器<init>()不一樣,虛擬機保證會在調用前先調用其父類的<clinit>(),所以不須要顯式調用父類構造器
  • 父類的<clinit>()對類變量的賦值操做優先於子類的<clinit>()執行
  • <clinit>()並不是必需,若是類中無靜態代碼塊或對類變量的賦值操做,那麼編譯器能夠不生成<clinit>()方法,Class字節碼中也就沒有<clinit>()方法
  • 接口無靜態代碼塊可是能夠用類變量賦值操做,所以也會生成<clinit>方法,可是不須要先調用父接口的<clinit>()方法,只有父接口的類變量使用時才調用<clinit>()方法初始化父接口
  • 虛擬機會保證多線程環境下類的<clinit>()方法能夠阻塞地調用,即線程T1調用類C的<clinit>()方法初始化C的過程當中,線程T2會阻塞而沒法進入類C的<clinit>()方法中的

使用 Using

卸載 Unloading

相關文章
相關標籤/搜索