我所理解的JVM(二):類加載機制

類在虛擬機中的生命週期:加載(Loading)、驗證(Verification)、準備(preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸載(Unloading) 共7個階段java

加載、驗證、準備、初始化、卸載 這5個階段有嚴格的前後順序關係。其餘階段不必定。(爲了支持java語言的運行時綁定)spring

加載由3部分組成:json

  1. 這裏是列表文本經過classloader獲取該類的二進制字節流。
  2. 將字節流所表明的靜態存儲結構轉化爲運行時數據結構。
  3. 在內存中生成一個表明這個的java.lang.Class對象,做爲方法去這個類的各類數據的訪問入口。

第1點是很是靈活的,好比能夠從jar包獲取,能夠從網絡獲取,能夠動態生成(動態代理、數組類),能夠由其餘文件生成(好比jsp)等等。設計模式

class文件運行與JVM中。.class能夠是.java文件經過javac編譯而成,也能夠是groovy文件經過groovyc編譯而成,也能夠是JRuby文件經過JRbubyc編譯而成,也能夠是其餘語言程序(好比scala)經過對應的編譯器編譯而成。因此JVM對class文件的驗證就顯得很是重要,以防止對正在運行的JVM形成傷害。數組

驗證包括一下幾部分網絡

  1. 文件格式驗證:是否已魔法書0xCAFEBABE開頭,主次版本號是不是當前JVM能夠接受的,常量池中的常量是否有不被支持的常量類型,常量池中的各類索引值是否有指向不存在的常量或不符合類型的常量。。。。。等等
  2. 元數據驗證:是否有父類,父類是否不容許被繼承,是否實現了父接口的接口或者父抽象類的抽象方法,類的字段、方法是否與父類產生矛盾(父final變量不可覆蓋,父final方法不可重寫)。。。。等等
  3. 字節碼驗證。主要是對方法體內的代碼進行驗證,好比參數類型是否一致,類型轉換是否有效。。。等等
  4. 符號引用驗證。這一驗證階段配合第三階段--解析階段完成。主要驗證引用類是否存在,類、字段、方法的訪問性 等等。。。

準備:爲類變量分配內存並設置默認初始值(0,false,null等)。若是爲final,則分配內存並設置值。數據結構

解析:虛擬機將常量池內的編譯器使用的符號引用替換爲運行期使用的直接引用的過程(配合驗證階段--的第4步)。多線程

符號引用:符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時可以無歧義的定位到目標便可。符號引用是由於在編譯的時候,不能明確所引用的類或屬性或方法在運行時的具體地址,因此就以符號引用來代替。jvm

直接引用:在運行期能直接肯定目標的指針、相對偏移量或間接定位的句柄。HotSpot採用指針。jsp

JVM規範並未規定解析階段發生的具體時間。只要求在16個特定的字節碼指定執行以前,先對他們所使用的符號引用進行解析。因此虛擬機實現能夠根據須要判斷究竟是在類唄加載器加載時就對常量池中的符號引用進行解析,仍是等到一個符號引用將要被使用以前纔去解析它。通常來講如今的虛擬機都選擇後者,這樣能夠邊使用邊解析,提升訪問速度(暫時不解析還沒用使用到的)。

初始化:執行<clint>()方法 字節碼中的<clint>()方法由編譯器對類變量的賦值動做和靜態代碼塊按代碼編寫順序合併而來。若是一個類沒有類變量也沒有靜態代碼塊,則該類編譯成的字節碼中沒有<clint>()方法。 由虛擬機來保障在多線程的狀況下,只有一個線程去執行這個類的<clint>()方法,其餘線程阻塞等待直到<clint>()完成。

類加載器

  1. 啓動類加載器:Bootstrap ClassLoader 負責加載<JAVA_HOME>/lib目錄下的文件(如rt.jar)。
  2. 擴展類加載器:Extension ClassLoader 負責加載<JAVA_HOME>/lib/ext目錄下的問題件。
  3. 應用程序類加載器:Application ClassLoader 負責加載ClassPath上的指定的類庫。

類加載器比較靈活,開發者能夠繼承java.lang.ClassLoader來實現本身的類加載器(好比spring、fastjson等都有類加載器的實現)。

雙親委派: 當碰到一個須要被加載的Class的時候,該加載器會委託父加載器加載。當父加載器範圍未加載而且不能加載該類的時候,纔有該加載器加載類。這樣作的目的是爲了保障加載到的class文件存放到內存中的java.lang.Class對象是同一個。父加載器與子加載器不是經過繼承實現的,而是組合。 注意:不一樣ClassLoader加載同一個class文件,會生成不一樣的java.lang.Class對象,致使java代碼中 instanceof 返回false

class文件以何種格式存儲,類型什麼時候加載、如何鏈接,以及虛擬機如何執行字節碼指令都是有虛擬機直接控制的行爲,用戶編寫的程序代碼沒法直接控制和改變。用戶能經過程序進行操做的,主要是字節碼的生成與類加載器這兩部分。

須要對類進行初始化的場景:

  1. new對象、設置或讀取靜態變量(同時被被final修飾除外,由於編譯期已經把結果放入常量池)、調用類的靜態方法
  2. 對類進行反射調用的時候
  3. 若是父類沒有初始化,須要先初始化父類
  4. 程序入口main方法所在的類

注意

  1. 經過子類來調用父類的靜態變量或方法,不會初始化子類;
  2. 定義數組的時候不會初始化類;
  3. 調用 fianl static 變量 不會初始化類。編譯器已經將被調用的final值放在了調用者的常量池中。
  4. 只有在實際使用到父接口(好比使用父接口定義的常量)纔會初始化父接口。(只經過java代碼很差證實,能夠配合使用jvmconsole等工具查看已加載類)
  5. 內部類會執行懶加載,第一次使用到的時候才執行初始化。(單例設計模式的一種)

獲取class對象的三種方式:

  1. TestA.class;
  2. Class.forName("com.a.b.c.TestA.class");
  3. testA.getClass();

這三種方式中,第一種若是是首次加載只會觸發TestA對應的class的加載、驗證、準備,不必定會有解析,但必定不會初始化(即,static代碼塊不會執行)。第二種方式若是是首次加載會在第一種的基礎上多執行一次初始化。第三種方式該類的對象都已經存在了,說明該類已經進行過來初始化。

參考資料:

  1. 《深刻理解Java虛擬機》
  2. JVM類加載的原理及實現
相關文章
相關標籤/搜索