深入理解JVM一加載機制

人若無名 便可專心練劍

引子

這裏寫圖片描述
如圖,這是java代碼到最終執行程序的過程。

1.java代碼——>靜態編譯(javac)——>byteCode(.class)(通常爲靜態編譯,除了特殊情況,如動態代理)
2.Loading——>Linking(vertify—>prepare—>resolve)——>initialization——Memory——>Execution engine(運行時)

本節我們重點關注classLoader中的加載、連接、初始化過程。

類或接口的載入過程(class loading subsystem)

這裏寫圖片描述

(以下所說的"類"等價於 java類,java接口)

Loading (加載)

“加載(Loading) ”是“類加載”(Class Loading)過程的一個階段,希望讀者沒有混淆這兩個看起來很相似的名詞。
Loading中完成的工作:
1. 通過一個類的全限定名獲取這個類對應的byteCode 二進制流。
思考:從哪裏獲取?
通常有如下幾種方式:
- jar、war等壓縮包文件中獲取。
- 網絡中獲取二進制流數據。如applet程序
- 動態獲取,運行時產生字節碼,並載入。如動態代理
2. 將這個二進制流的byteCode格式轉換爲MethodArea的存儲格式,放入MethodArea。
3. 在內存中生成一個對應的class對象,作爲方法區這個類各種數據的訪問入口。

簡而言之:加載就是把字節碼載入MethodArea,並生成對應Class對象,以供運行使用。

連接(Linking)

連接中的具體步驟不一定是要等loading完成才能進行,一般都會在loading開始運行後交叉執行連接動作。但是,loading與linking的開始時間是按照先後的順序。

連接中有三個大的步驟:驗證——>準備——>解析
一.驗證 (verification)
驗證 (verification)爲了保證 class文件對應的字節碼二進制流是否符合當前JVM運行的要求,不會對JVM造成危害。驗驗證失敗拋出java.lang.VerifyError異常。

  1. 文件格式驗證
    主要驗證加載到的字節碼流是否符合class文件的格式規範,並且驗證是否能被當前版本的JVM處理(JVM向低版本兼容),
    如:
    是否以魔數0xCAFEBABE開頭;
    主、 次版本號是否在當前虛擬機處理範圍之內;
    常量池的常量中是否有不被支持的常量類型(檢查常量tag標誌);
    指向常量的各種索引值中是否有指向不存在的常量或不符合類型的常量;CONSTANT_Utf8_info型的常量中是否有不符合UTF8編碼的數據;
    Class文件中各個部分及文件本身是否有被刪除的或附加的其他信息…

文件格式驗證成功,則會把class文件(bytecode)格式轉換爲MethodArea的特定格式結構,接下來的步驟都會在這個MethodArea格式基礎上進行。

2… 元數據驗證
主要是校驗元數據是否符合JVM規範。
如:
這個類是否有父類(除了java.lang.Object之外,所有的類都應當有父類);這個類的父類是否繼承了不允許被繼承的類(被final修飾的類);
如果這個類不是抽象類,是否實現了其父類或接口之中要求實現的所有方法;類中的字段、 方法是否與父類產生矛盾(例如覆蓋了父類的final字段,或者出現不符合規則的方法重載,例如方法參數都一致,但返回值類型卻不同等)

3… 字節碼驗證
主要是驗證分析方法體。

4… 符號引用驗證
可以看做是對常量池中對各個數據的引用是否能找到對應的數據。
如:
符號引用中通過字符串描述的全限定名是否能找到對應的類;
在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段;
符號引用中的類、 字段、 方法的訪問性(private、 protected、 public、 default)是否可被當前類訪問;

通常這個階段可能拋出的異常:

java.lang.IllegalAccessError、
java.lang.NoSuchFieldError、
java.lang.NoSuchMethodError

如果確認代碼不需要驗證(如,生成上的老代碼),-Xverify:none參數來關閉大部分的類驗證措施,以縮短虛擬機類加載的時間

二.準備 (prepare)

準備階段就是爲static變量分配內存空間(MethodArea中)、並設置爲初始值。

比如:

	static String bbve = "ABC";

在這個階段會給bbve變量在methodArea中分配內存空間,並設置爲初始值(null)。在這個階段並沒有將“ABC”賦值給變量。如果用了 static final修飾,會在準備階段完成賦值。

三.解析 (resolve)
解析過程就是把class文件常量池中的符號引用(一部分)轉換爲內存中的直接引用的過程,這種解析完成的前提是在編譯器就可以確定要執行的具體方法、具體類型。

初始化

初始化時class加載的最後一步,之前的步驟除了loading可以由用戶自定義的classLoader加載之外,其餘都是有JVM控制的。

初始化階段開始真正的執行字節碼,其實就是執行字節碼的指令,執行static變量的賦值操作、執行static塊;

並且這個階段,由JVM自身保證各個class的同步(如果多
個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的<clinit>()方法), 並且一個class只會初始化一次。

雙親委派模型

這裏寫圖片描述

Bootstrap加載器負責將存放在JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的。

雙親委派模式的工作流程:

加載class二進制流的時候,通常情況下,當前類加載器不會加載,會把加載任務委派給當前加載器的父類去加載,除了Bootstrap之外的每個加載器都是如此。所以類的加載工作總是會由Bootstrap去執行,當Bootstrap執行失敗(沒有找到這個類),才使用子類去加載。

非雙親委派模式
並不是所有程序都遵守上述模式,根據特殊場景需要,需要打破雙親委派的模式。如:使用 Thread.setContextClassLoader(ClassLoader cl) 去實現父類加載器請求子類加載器執行加載請求。(JNDI、 JDBC、 JCE、 JAXB和JBI等);OSGi中也爲了特殊場景需要,改變了加載規則。可參
http://blog.onlycatch.com/post/Java類加載機制

發佈了126 篇原創文章 · 獲贊 196 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章