12 類的載入過程
阿新 • • 發佈:2019-02-06
《深入理解Java虛擬機器:JVM高階特性與最佳實踐(第2版)》7.3節
類的生命週期:
載入、驗證、準備、初始化和解除安裝5個階段必須按順序開始,不一定按順序進行、結束 這些階段通常是交叉進行的(在一個階段執行過程中呼叫、啟用另外一個階段) 解析階段在某些情況下可在初始化階段後開始,目的是為了支援Java語言的執行時繫結(也稱動態繫結、晚期繫結)
目的:為確保Class檔案位元組流中包含的資訊符合當前虛擬機器要求,且不會危害虛擬機器自身安全
該階段工作量在虛擬機器類載入子系統中佔相當大一部分
若驗證到輸入位元組流不符合Class檔案格式約束,虛擬機器丟擲java.lang.VerifyError異常或其子類異常
4個子階段(《Java虛擬機器規範(JavaSE7版)》起):
1). 檔案格式驗證
驗證位元組流是否符合Class檔案格式規範,且能被當前版本虛擬機器處理
基於二進位制位元組流進行,通過該子階段驗證後位元組流才會儲存到方法區中進行儲存,以後3個子階段全部基於方法區儲存結構進行
驗證點:魔數,主、次版本號,常量池常量,各種索引值,等
2). 元資料驗證
對位元組碼描述資訊進行語義分析,保證其描述的資訊符合Java語言規範要求
校驗的是元資料的資料型別
驗證點:是否有父類,是否繼承了不允許被繼承的類,非抽象類是否實現了其父類或介面要求實現的所有方法,類中欄位、方法是否與父類產生矛盾,等
3). 位元組碼驗證
通過資料流和控制流分析,確定程式語義是合法的、符合邏輯的
最複雜的子階段
驗證點:保證跳轉指令不會跳轉到方法體以外位元組碼指令,保證方法體中的型別轉換有效,等
4). 符號引用驗證
確保解析動作能正常執行,可看做是對類自身以外(常量池中各種符號引用)資訊進行匹配性校驗
發生在虛擬機器將符號引用轉化為直接引用時,該轉化動作在連線第三步,解析階段中發生
重要但非必要階段,可用-Xverify:none關閉大部分類驗證措施
驗證點:符號引用中通過字串描述的全限定名是否能找到對應類,在指定類中是否存在符合方法的欄位描述符及簡單名稱所描述的方法和欄位,符號引用中類、欄位、方法的訪問性(private、protected、public、default)是否可被當前類訪問,等
無法通過該子階段驗證會丟擲java.lang.IncompatibleClassChangeError異常子類,例java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等
正式為類變數(static修飾的變數)分配記憶體並設定類變數初始值(零值或程式碼定義值),這些變數使用的記憶體都在方法區分配
有static、無final修飾的變數,初始值為零值,為變數賦程式碼中的值操作在類構造器<clinit>()方法中
有static、有final修飾的變數,初始值為程式碼中的值,該類欄位在欄位屬性表中有ConstantValue屬性
基本資料型別的零值:
在該階段之前的類載入過程中,除載入子階段,使用者應用程式可通過自定義類載入器參與類載入外,其餘動作完全由虛擬機器主導和控制
該階段真正開始執行類中定義的Java程式碼(或者說位元組碼),根據程式制定的計劃初始化類變數和其他資源
從另外一個角度表達:該階段執行類構造器<clinit>()方法
<clinit>()方法的生成:
編譯器自動收集類中所有類變數的賦值動作和靜態語句塊(static{}塊)中的語句合併產生
編譯器收集的順序由語句在原始檔中出現的順序決定
靜態語句塊中只可訪問定義在靜態語句塊之前的變數,定義在之後的變數,靜態語句塊可賦值但不可訪問,即禁止“非法向前引用”
若類或介面中無靜態變數賦值、無靜態語句塊,則可不生成該方法
<clinit>()方法的執行:
虛擬機器會保證子類(與介面區分)<clinit>()方法執行前,父類<clinit>()方法已經執行完畢,不需顯式呼叫父類<clinit>()方法(例項構造器<init>()方法需顯式呼叫)
這意味著父類中定義的靜態變數賦值、靜態語句塊執行優先於子類
第一個被執行<clinit>()方法的類一定是java.lang.Object
介面的<clinit>()方法執行時不需要先執行父介面的<clinit>()方法,當父介面的變數被使用時才執行<clinit>()方法
虛擬機器會保證<clinit>()方法在多執行緒環境中被正確地加鎖、同步,多執行緒執行會阻塞(該阻塞較隱蔽),直到活動執行緒執行<clinit>()方法完畢
載入、驗證、準備、初始化和解除安裝5個階段必須按順序開始,不一定按順序進行、結束 這些階段通常是交叉進行的(在一個階段執行過程中呼叫、啟用另外一個階段) 解析階段在某些情況下可在初始化階段後開始,目的是為了支援Java語言的執行時繫結(也稱動態繫結、晚期繫結)