JVM 類的加載全過程

  • 加載階段

  1. 加載階段需要虛擬機做三件事:
    1. 虛擬機通過一個類的全限定名來獲取描述定義它的二進制字節流
    2. 將其字節流的靜態存儲結構轉換爲方法區的運行時數據結構
    3. 在堆中生成一個對應着該類的java.lang.Class對象作爲程序訪問方法區中該類型的訪問入口
  2. 數組類型與非數組類型在加載階段有所區別
    1. 非數組類型加載階段比較自由,既可以使用虛擬機內置的引導類加載器也可以使用自定義的類加載器。開發人員可以通過自定義的類加載器控制字節流的獲取(自定義類加載器需要重寫findClass()和loadClass()方法)
    2. 數組類型不需要類加載器來創建,它是由java虛擬機內存中動態構造出來的,但是數組類的元素類型最終還是需要由類加載器來完成其加載
  3. 加載之後虛擬機外部的二進制字節流會按照虛擬機設定的格式存儲於內存中,之後會在堆中創建一個與該類相對應的java.lang.Class類的對象用於該類型在方法區中被各種數據的訪問外部接口
  4. 加載階段會與解析階段混合進行,夾雜在加載階段的一些操作屬於解析階段,但這並不影響二者的開始時間和執行順序
  • 驗證階段

  1. 連接階段第一步;驗證包含類的內容class文件的全部內容是否符合Java虛擬機規範,其內容作爲代碼運行於虛擬機中是否會危害虛擬機自身安全;驗證階段需要非常關鍵(承上啓下),直接決定虛擬機是否能夠承受惡意代碼的攻擊
  2. 從整體上來看可以分爲四個部分
    1. 文件格式驗證:驗證class文件,判斷其版本
      1. 主次版本號是否能被當前虛擬機所接受,並處於其處理範圍
      2. 常量池中的所有常量是否存在不被允許的常量類型
      3. class文件中的各個數據項以及本身是否被篡改
    2. 元數據驗證:查看程序是否符合java語義規範
      1. 該類是否有父類(雖然java不許多繼承但是除了java.lang.Object沒有父類之外,都該有)
      2. 該類的父類是否繼承了不被允許繼承的類(被final修飾等等)
      3. 若非抽象類,是否實現了來自父類/接口中的所有方法
    3. 字節碼驗證:通過控制流分析和數據流分析,判斷程序語義是否符合邏輯概念,對類的方法體(Class文件中的Code屬性(代碼))進行校驗,確保類中代碼在虛擬機中執行不會危害虛擬機安全
      1. 操作數棧放置了一個float類型數據,卻按照double類型存入本地變量表中;這種操作不允許出現
      2. 跳轉指令跳轉到了方法體以外的字節碼指令上也是不被允許的
    4. 符號引用驗證:檢查類是否缺少或者被禁止訪問它所依賴的外部類或者是字段,方法,接口等資源
      1. 符號引用中的字符串描述的全限定名是否能夠定位到類
      2. 符號引用中的字段、方法、接口的可訪問度,是否能被當前類訪問
  3. 驗證階段不是必須要執行,如果程序的所有代碼都已經驗證過執行過很多次,可以關閉大部分的類驗證,節省類加載過程的效率
  • 準備階段

  1. 連接階段第二步;對類中所定義的變量(static變量,即靜態變量)進行內存分配並設置初始值的過程
  2. 從概念上來看,應在方法區中分配內存,但方法區是邏輯上的區域
    1. JDK7及之前,虛擬機團隊使用永久代實現方法區,實現方法區的邏輯概念
    2. JDK8及之後,類變量會隨着class對象一起分配到Java堆中
  3. 易混淆之二:
    1. 此時分配內存的只有類變量沒有實例變量,實例變量會在實例初始化時被放到Java堆中
    2. 通常情況下初始值爲該數據類型的零值
  4. 不太通常的情況下,類字段的字段屬性中包含ContantsValue屬性時,會將初始值設置爲該屬性的變量值
  • 解析階段

  1. 連接階段最後一步;解析階段是將常量池中的符號引用替換爲直接引用的過程
  2. 發生時間:虛擬機規範中並未強制解析何時執行,只描述了在執行17條指令(new、getStatic、putStatic、invokeStatic、invokeDynamic等)之前,對常量池中的符號因用進行解析。虛擬機會根據需要自行判斷採用何種方式:是在類被類加載器加載前對常量池中的符號引用進行解析還是在符號引用即將被引用之前再解析
  3. 關於緩存:
    1. 每個符號引用都有可能會被解析多次,除invokeDynamic指令之外,虛擬機會將第一次解析結果緩存下來
    2. invokeDynamic指令會在程序快要執行到這行代碼時纔會解析,其引用被稱爲動態調用點限定符
  4. 解析動作*7:類或接口、字段、類方法、接口方法、方法類型、方法句柄、動態調用點
  • 初始化階段

  1. 類加載過程最後一步;在此階段虛擬機將開始執行類中的代碼,並將主導權移交給應用程序
  2. 與其說是初始化階段不如說是類構造器<clinit>()方法的執行過程,該方法是javac編譯器的自動生成物
    1. <clinit>()方法由編譯器自動收集類中的變量的複製動作以及靜態語句塊(static{})的語句組成;收集順序由語句在源文件中的順序決定;靜態語句塊只能訪問自己以及之前的變量,不能訪問靜態語句塊之下的內容,但可以在static塊中爲其賦值

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章