JVM 中類加載的鏈接與初始化
1. 前言
對于類加載子系統(tǒng),前邊的課程已經(jīng)對加載(Loading)這一步做了詳細的講解,本節(jié)主要對類加載子系統(tǒng)加載步驟中的鏈接與初始化進行講解。本節(jié)主要知識點如下:
- 鏈接(Linking)步驟更加詳細的模塊劃分:驗證,準備和解析,為本節(jié)基礎知識點;
- 掌握在鏈接(Linking)步驟中的第一步驗證的詳細驗證內容,為本節(jié)重點內容之一;
- 掌握在鏈接(Linking)步驟中的第二步準備的準備內容,為本節(jié)重點內容之一;
- 掌握在鏈接(Linking)步驟中的第三步解析的具體解析內容,為本節(jié)重點內容之一;
- 掌握初始化(Init)步驟中的規(guī)則以及實例初始化順序,為本節(jié)重點內容之一。
通篇皆為重點內容,本節(jié)知識也會為類加載子系統(tǒng)部分畫上一個完美的句號,一定要認真對待。
2. 類加載子系統(tǒng)知識回顧
我們在JVM 總體架構的講解過程中,提到過類加載子系統(tǒng)的工作流程分為三步:加載->鏈接->初始化。如下圖所示:
本節(jié)我們所討論的內容都是圍繞第二步“鏈接(Linking)” 和第三步“初始化(Init)”進行的。
我們將鏈接(Linking)這一步,再進行下細致的模塊劃分,如下圖所示:
從上圖中我們可看到,鏈接(Linking)這一步,里邊包含了三個更加細致的步驟,分別為驗證(verify),準備(prepare)和解析(resolve)。后文我們會對這三個步驟進行講解。
3. 鏈接-驗證(verify)
定義:驗證是連接階段的第一步,這一階段的目的是為了確保 Class 文件的字節(jié)流中包含的信息符合當前虛擬機的要求,并不會危害虛擬機的自身安全。
驗證過程的主要驗證信息:驗證過程中,主要對三種類型的數(shù)據(jù)進行驗證,分別是“元數(shù)據(jù)驗證,字節(jié)碼驗證和符號引用驗證”。具體內容請看下邊的講解。
元數(shù)據(jù)驗證:
- 驗證這個類是否有父類(除了 java.lang.Object 之外,所有類都應當有父類);
- 驗證這個類是否繼承了不允許被繼承的類(被 final 修飾的類);
- 如果這個類不是抽象類,驗證該類是否實現(xiàn)了其父類或接口之中所要求實現(xiàn)的所有方法;
- 驗證類中的字段、方法是否與父類產(chǎn)生矛盾(例如覆蓋了父類的 final 字段,或者出現(xiàn)不符合規(guī)則的方法重載,例如方法參數(shù)都一致,但返回值類型卻不同等等)。
字節(jié)碼驗證:字節(jié)碼驗證主要目的是通過數(shù)據(jù)流和控制流分析,確定程序語義是合法的、符合邏輯的。這個階段將對類的方法體進行校驗分析,保證被校驗類的方法在運行時不會產(chǎn)生危害虛擬機安全的事件,例如:
- 保證任意時刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能配合工作。例如不會出現(xiàn)類似這樣的情況:在操作數(shù)棧放置了一個int類型的數(shù)據(jù),使用時卻按long類型來加載入本地變量表中;
- 保證跳轉指令不會跳轉到方法體以外的字節(jié)碼指令上;
- 保證方法體中的類型轉換是有效的,例如可以把一個子類對象賦值給父類數(shù)據(jù)類型,但是把父類對象賦值給子類數(shù)據(jù)類型,甚至把對象賦值給與它毫無繼承關系、完全不相干的一個數(shù)據(jù)類型,則是危險不合法的。
符號引用驗證:符號引用驗證可以看作是類對自身以外(常量池中的各種符號引用)的信息進行匹配性校驗,通常需要校驗以下內容:
- 符號引用中通過字符串描述的全限定名是否能夠找到對應的類;
- 在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段;
- 符號引用中的類、字段、方法的訪問性(private、default、protected、public)是否可被當前類訪問。
4. 鏈接-準備(prepare)
定義:準備階段是正式為類變量分配內存并設置類變量默認值(通常情況下是數(shù)據(jù)類型的零值)的階段,這些變量所使用的內存都將在方法區(qū)中進行分配。這時候進行內存分配的僅包括類變量(被static修飾的變量),而不包括實例變量,實例變量將會在對象實例化的時候隨著對象一起分配在Java堆中。
Tips:準備階段是設置類變量的默認值,不同類型的類變量的默認值是不同的。變量默認值的對照表請參看下表:
變量類型
默認值
int
0
long
0L
short
0
char
‘\u0000’
byte
0
boolean
false
folat
0.0f
double
0.0d
reference
null
5. 鏈接-解析(resolve)
定義:解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。
Tips:定義中又引出了兩個新的概念:符號引用和直接引用。想要理解解析,必須要先搞明白什么是符號引用和直接引用。
符號引用(Symbolic References):符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。
直接引用(Direct References):直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。如果有了直接引用,那么引用的目標一定是已經(jīng)存在于內存中。
解析過程具體的解析內容:解析過程中,主要對如下4種類型的數(shù)據(jù)進行驗證:
- 類或接口的解析;
- 字段解析;
- 類方法解析;
- 接口方法解析。
6. 初始化
定義:進行準備階段時,變量已經(jīng)賦過一次系統(tǒng)要求的初始零值,而在初始化階段,則會根據(jù)程序員通過程序編碼制定的主觀計劃去初始化類變量和其他資源。
類的初始化階段是類加載過程的最后一個步驟,之前介紹的幾個類加載的動作里,除了在加載階段用戶應用程序可以通過自定義類加載器的方式局部參與外,其余動作都完全由Java虛擬機來主導控制。直到初始化階段,Java虛擬機才真正開始執(zhí)行類中編寫的 Java 程序代碼,將主導權移交給應用程序。
實例的初始化順序:在進行初始化時,實例變量的初始化順序如下圖所示:
實例的初始化順序是非常重要的知識點,在面試過程中也經(jīng)常涉及到這個知識點,上圖的加載順序需要重點掌握。
7. 小結
到目前為止,類加載器子系統(tǒng)就全部講解完成了。我們學習了類的加載,三種類加載器,雙親委派模型以及本節(jié)所講述的鏈接與初始化,其中對鏈接有細分了三個步驟進行了講解。
類加載器子系統(tǒng)是非常重要的 JVM 模塊,需要用心學習,對于一些概念性知識要增強理解,原理性知識要深入思索。后續(xù)我們會繼續(xù)講解 JVM 的其他重要模塊。