類在虛擬機中的生命週期:加載(Loading)、驗證(Verification)、準備(preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸載(Unloading) 共7個階段java
加載、驗證、準備、初始化、卸載 這5個階段有嚴格的前後順序關係。其餘階段不必定。(爲了支持java語言的運行時綁定)spring
加載由3部分組成:json
第1點是很是靈活的,好比能夠從jar包獲取,能夠從網絡獲取,能夠動態生成(動態代理、數組類),能夠由其餘文件生成(好比jsp)等等。設計模式
class文件運行與JVM中。.class能夠是.java文件經過javac編譯而成,也能夠是groovy文件經過groovyc編譯而成,也能夠是JRuby文件經過JRbubyc編譯而成,也能夠是其餘語言程序(好比scala)經過對應的編譯器編譯而成。因此JVM對class文件的驗證就顯得很是重要,以防止對正在運行的JVM形成傷害。數組
驗證包括一下幾部分網絡
準備:爲類變量分配內存並設置默認初始值(0,false,null等)。若是爲final,則分配內存並設置值。數據結構
解析:虛擬機將常量池內的編譯器使用的符號引用替換爲運行期使用的直接引用的過程(配合驗證階段--的第4步)。多線程
符號引用:符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時可以無歧義的定位到目標便可。符號引用是由於在編譯的時候,不能明確所引用的類或屬性或方法在運行時的具體地址,因此就以符號引用來代替。jvm
直接引用:在運行期能直接肯定目標的指針、相對偏移量或間接定位的句柄。HotSpot採用指針。jsp
JVM規範並未規定解析階段發生的具體時間。只要求在16個特定的字節碼指定執行以前,先對他們所使用的符號引用進行解析。因此虛擬機實現能夠根據須要判斷究竟是在類唄加載器加載時就對常量池中的符號引用進行解析,仍是等到一個符號引用將要被使用以前纔去解析它。通常來講如今的虛擬機都選擇後者,這樣能夠邊使用邊解析,提升訪問速度(暫時不解析還沒用使用到的)。
初始化:執行<clint>()方法 字節碼中的<clint>()方法由編譯器對類變量的賦值動做和靜態代碼塊按代碼編寫順序合併而來。若是一個類沒有類變量也沒有靜態代碼塊,則該類編譯成的字節碼中沒有<clint>()方法。 由虛擬機來保障在多線程的狀況下,只有一個線程去執行這個類的<clint>()方法,其餘線程阻塞等待直到<clint>()完成。
類加載器:
類加載器比較靈活,開發者能夠繼承java.lang.ClassLoader來實現本身的類加載器(好比spring、fastjson等都有類加載器的實現)。
雙親委派: 當碰到一個須要被加載的Class的時候,該加載器會委託父加載器加載。當父加載器範圍未加載而且不能加載該類的時候,纔有該加載器加載類。這樣作的目的是爲了保障加載到的class文件存放到內存中的java.lang.Class對象是同一個。父加載器與子加載器不是經過繼承實現的,而是組合。 注意:不一樣ClassLoader加載同一個class文件,會生成不一樣的java.lang.Class對象,致使java代碼中 instanceof 返回false
class文件以何種格式存儲,類型什麼時候加載、如何鏈接,以及虛擬機如何執行字節碼指令都是有虛擬機直接控制的行爲,用戶編寫的程序代碼沒法直接控制和改變。用戶能經過程序進行操做的,主要是字節碼的生成與類加載器這兩部分。
須要對類進行初始化的場景:
注意,
獲取class對象的三種方式:
這三種方式中,第一種若是是首次加載只會觸發TestA對應的class的加載、驗證、準備,不必定會有解析,但必定不會初始化(即,static代碼塊不會執行)。第二種方式若是是首次加載會在第一種的基礎上多執行一次初始化。第三種方式該類的對象都已經存在了,說明該類已經進行過來初始化。
參考資料: