我們都知道以 結尾的 Java 源文件,經過編譯之後會變成 結尾的位元組碼文件。JVM 通過類載入器來載入位元組碼文件,然後再執行程式。 什麼時候載入一個類 那麼, 什麼時候類載入器會載入一個類呢? 用到這個類的時候,JVM的類載入器就會載入這個類。用到這兩個字說起來很抽象,我用代碼和圖例來說明。 有下 ...
我們都知道以 .java
結尾的 Java 源文件,經過編譯之後會變成 .class
結尾的位元組碼文件。JVM 通過類載入器來載入位元組碼文件,然後再執行程式。
什麼時候載入一個類
那麼,什麼時候類載入器會載入一個類呢?用到這個類的時候,JVM的類載入器就會載入這個類。用到這兩個字說起來很抽象,我用代碼和圖例來說明。
有下麵這樣一段代碼,一個類EmergencyPlan
,裡面有一個main()
函數,main()
函數做的事情是創建了一個 Account 對象。
public class EmergencyPlan {
public static void main(String[] args) {
Account account = new Account();
}
}
我們應該知道運行 JVM 就相當於啟動了一個 Java 的進程,它會從程式的主函數,即main()
函數開始執行。所以類載入的步驟是這樣的:
- 先載入主函數所在的類
EmergencyPlan
- 由於
EmergencyPlan
使用了Account
,所以繼續載入Account
準備和初始化的區別
類載入機制總共有這樣7個步驟:載入 -> 驗證 -> 準備 -> 解析 -> 初始化 -> 使用 -> 卸載。接下來先把每個階段在做什麼講一下,再著重對比一下準備階段和初始化階段。
- 驗證階段:驗證位元組碼文件是否符合JVM的規範。這挺好理解,萬一位元組碼文件被修改過,JVM壓根無法執行咋辦。所以載入之後先驗證一下。
- 準備階段:為類分配記憶體空間,為變數賦初值。
- 解析階段:符號引用替換為直接引用。
- 初始化階段:執行初始化代碼,new對象;執行static代碼塊;父類沒有初始化要先初始化父類。
用代碼和畫圖來說明一下準備階段和初始化階段。
public class EmergencyPlan {
public static int id
= Configuration.getInt("plan_id");
}
這段代碼說的是EmergencyPlan
這個類有一個變數id
,通過getInt()
為其賦值:
- 準備階段會為
id
開闢一個記憶體空間,但不會執行賦值操作,僅僅是賦予一個初值0。 - 初始化階段才會執行
getInt()
為變數id
初始化值。
類載入的過程就變成了下圖所示的樣子:
類載入器和雙親委派模型
JVM進行類載入是通過類載入器完成,類載入器是一種親子層級結構的模型。Java裡面的類載入器有這樣幾種:
- 啟動類載入器。載入 JDK 中 lib 目錄中 Java 的核心類庫,即
$JAVA_HOME/lib
目錄。 - 擴展類載入器。載入 lib/ext 目錄下的類。
- 應用程式類載入器。載入我們寫的應用程式。
- 自定義類載入器。根據自己的需求定製類載入器。
那什麼是雙親委派模型呢?類載入器是一種親自層級結構,就像下圖所示:
比如要載入上面的EmergencyPlan
類,應用程式類載入器會先問它的父親擴展類載入器,你能幫我載入麽?擴展類載入器會再問它的父親啟動類載入器,你能幫我載入麽?
顯然EmergencyPlan
是一個應用程式類。啟動類載入器會告訴擴展類載入器,你自己去載入;擴展類載入器就會告訴應該程式類載入器,你自己去載入。最後,應用程式類載入器就自己載入了EmergencyPlan
。
流程圖總結
最後來在類載入的流程圖上,把雙親委派模型也添加上去。