JVM學習筆記——類的載入機制
摘要:
類載入的過程:載入、驗證、準備、解析、初始化
載入
通過類的全類名獲取定義此類的二進位制位元組流
通過位元組流獲取靜態儲存結構並轉化為方法區的執行時資料結構
生成一個代表該類的java.lang.Class物件,作為訪問方法...
-
類載入的過程:載入、驗證、準備、解析、初始化
- 載入
- 通過類的全類名獲取定義此類的二進位制位元組流
- 通過位元組流獲取靜態儲存結構並轉化為方法區的執行時資料結構
- 生成一個代表該類的java.lang.Class物件,作為訪問方法區資料的入口。
- 驗證
- 檔案格式驗證,驗證位元組流是否Class檔案格式的規範,檔案能否被當前版本的虛擬機器處理。
- 元資料驗證,對位元組碼資訊進行語義分析,保證其符合Java語法。
- 位元組碼驗證,針對方法內指令級別的驗證,保證位元組碼指令不會危害虛擬機器安全。
- 符號引用驗證,由符號引用轉為直接引用時的驗證,即發生在“解析”階段。驗證符號引用的內容是否能找到匹配的資訊,例如:符號引用中通過全類名能找到對應的類,符號引用中的資訊,可見性(private、public..)是否能被當前類訪問等等。
- 準備
為類分配記憶體,設定類變數預設初始值。 - 解析
將類內部的“符號引用”替換為“直接引用”。替換的範圍包括,類或介面名,欄位名,類方法名,介面方法名。- 符號引用
以一組符號來描述所引用的目標,比如String s = "a"
,使用s時就會被解析成符號引用。 - 直接引用
可以直接指向目標的指標、偏移量或是一個能間接定位到目標的控制代碼。比如System.out.print("abc")
,其中“abc”就會被解析為直接引用。
- 符號引用
- 初始化
生成並執行<clinit>()方法。- 生成
- <clinit>()方法會收集類變數初始賦值操作以及靜態程式碼塊的內容,按照原始檔中出現的順序進行收集。
- 在沒有類變數初始化操作以及靜態程式碼塊時,<clinit>()可以不生成。
- 執行
- JVM會保證父類的<clinit>()先於子類的<clinit>()執行。
- 介面與類不同,子介面的<clinit>()執行時,如果沒有引用父介面的變數,則父介面的<clinit>()不會執行。
- <clinit>()執行時JVM會對其進行加鎖,也就是說多個執行緒執行<clinit>()時,只有一個執行緒會執行,其他執行緒將會阻塞等待執行完畢。
- 生成
- 載入
-
類載入器
類載入器就是實現“載入”這個動作的元件。在Java中,兩個類是否“相等”,必須在這兩個類是由同一個類載入器載入的前提下。這裡的“相等”,包括:Class的equals()、Class的isInstance()、instanceof關鍵字等等。 -
雙親委派模型
所有類載入器遵循“父子”的層級關係(這裡的“父子關係一般使用複合而非繼承”),當一個類載入時優先委託給父級的載入器進行載入,若所有父級載入器都無法載入該類時,才會到本載入器載入。
-
OSGi的類載入模型
OSGi將每個模組稱為Bundle,與普通的Java類庫差別不大,都是Package與Class。Bundle將所依賴的Package通過Import-Packge進行宣告,將允許匯出的Package通過Export-Packge宣告。如果某個Bundle依賴了某個Package,那麼所有對這個Package內的類載入,全部會交給Export該Package的Bundle的類載入器進行處理。詳細的類載入規則如下:- 以java.*開頭的類,委派給父類載入器
- 否則,委派列表內的類委派給父類載入器
- 否則,Import列表中的類,委派給Export這個類的Bundle的類載入器。
- 否則,查詢當前Bundle的ClassPath,使用自己的類載入器。
- 否則,查詢是否存在自己的Fragement Bundle中,如果是則委派給Fragement Bundle的類載入器。
- 否則,查詢Dynamic Import列表的Bundle,委派給對應的Bundle的類載入器。
- 否則,類載入失敗。