類加載時機
虛擬機規(guī)范中沒有規(guī)定類加載的時機,但是規(guī)定了需要進行初始化的5種情況(而加載上陕、驗證桩砰、準備必須在此之前)。
- 遇到new释簿、getstatic亚隅、putstatic、invokestatic這四條字節(jié)碼指令時庶溶,也就是new一個對象或者讀寫一個類的字段或者調用一個類的方法煮纵。
- 通過反射調用一個類的時候。
- 初始化一個類是偏螺,如果父類還沒有初始化醉途,那么先初始化父類。
- 虛擬機啟動時砖茸,指定的含main()的主類隘擎。
- JDK1.7的動態(tài)語言支持,MethodHandle是一個類字段或者方法的句柄凉夯,如果這個類沒有初始化货葬,那么初始化。
不會初始化的情況:
- 子類引用父類的靜態(tài)字段和方法劲够,子類不會初始化震桶。
- 通過數組定義引用類,類不會初始化征绎。
- 引用可以在編譯期確定的常量類字段蹲姐,不會引起類初始化,因為這個常量已經被使用的類放入自身的常量池人柿。
- 一個接口初始化時不要求父接口完成初始化柴墩,只有在真正使用父接口時才需要初始化。
類加載的過程
指生命周期中的加載凫岖、驗證江咳、準備、解析和初始化
加載
- 虛擬機根據類的全限定名獲取類的二進制字節(jié)流哥放。
- 將字節(jié)流代表的靜態(tài)存儲結構轉化為方法區(qū)的運行時數據結構歼指。
- 內存中生成一個java.lang.Class對象爹土,作為方法區(qū)這個類的數據的入口。
獲炔壬怼:
- ZIP(jar, war, ear...)
- 網絡(applet)
- 運行時生成胀茵,Proxy動態(tài)代理
- 其他文件生成,JSP文件生成
- 數據庫
加載類的階段可以通過自定義類加載器來自定義挟阻,通過重寫loadClass()方法來控制獲取字節(jié)流宰掉。
但是數組類稍有特殊,例如數組類C:
(1)如果組件類型為引用類型赁濒,那么遞歸地加載這個組件類型轨奄,然后數組C將在加載了該組件類型的類加載器的類命名空間上被標識。
(2)如果組件類型不是引用類型拒炎,那么數組C會被標記為與引導類加載器關聯
(3)數組類的可見性和組件類型一致
存儲:
(1)二進制流以虛擬機所需格式存儲在方法區(qū)挪拟,格式由虛擬機實現自定。
(2)實例化一個java.lang.Class類的對象击你,HotSpot將這個對象放在了方法區(qū)玉组。
驗證
驗證字節(jié)流的信息符合虛擬機要求,并且不會危害虛擬機的安全丁侄。
字節(jié)碼層面來說惯雳,字節(jié)碼可以完成很多Java語言無法完成的操作,而字節(jié)碼可以使用很多方式生成鸿摇,不一定由Java源碼編譯而來石景,所以虛擬機為了保證自身安全,需要進行驗證拙吉。
- 文件格式驗證
- 魔數
- 主次版本號是否支持
- 常量池中的常量類型
- 指向常量的索引值指向是否有問題
- 常量池UTF8類型常量的UTF8編碼
- Class文件是否有被刪除或者附加的其他信息
......
這一階段的驗證是基于字節(jié)流的潮孽,通過后,字節(jié)流才會進入方法區(qū)筷黔,后面的驗證會基于方法區(qū)內的存儲結構往史,而不再操作字節(jié)流。
- 元數據驗證
進行語義分析佛舱,以保證滿足Java語言規(guī)范
- 除了Object外應該有父類
- 是否繼承了final類
- 如果不是抽象類椎例,是否實現了所有需要實現的方法。
- 字段與方法是否與父類矛盾(覆蓋了父類final字段请祖,不符合規(guī)則的重載)
......
- 字節(jié)碼驗證
通過數據流與控制流分析订歪,確定程序語義是合法符合邏輯的。
- 保證操作數棧的數據類型與字節(jié)碼指令能配合
- 跳轉指令不會跳轉到方法體之外的字節(jié)碼指令
- 類型轉換是有效的
......
方法體的Code屬性的屬性表中在JDK1.6后添加了StackMapTable损拢,用于描述方法的所有基本塊開始時本地變量表和操作棧應有的狀態(tài)陌粹,驗證期間,不需要在根據程序推導這些狀態(tài)的合法性福压,只需要檢查StackMapTable屬性中的記錄是否合法即可掏秩,這樣將類型推導轉變?yōu)轭愋蜋z查。
- 符號引用驗證
這個驗證發(fā)生在解析階段虛擬機將符號引用轉化為直接引用的時候荆姆,對類自身以外的信息進行匹配性校驗:
- 字符串描述的全限定名能否找到對應的類
- 指定類中是否存在符合方法描述符所描述的方法蒙幻,是否存在簡單名稱所描述的字段
- 符號引用中的類、字段胆筒、方法的訪問性是否可被當前類訪問
準備
為類變量在方法區(qū)中分配內存邮破,并設零值(零值而非初始值),賦初始值的指令在<clinit>()方法中仆救,在初始化階段執(zhí)行抒和。
但如果是final變量,字段屬性表中包括ConstantValue的屬性彤蔽,那么會設為ConstantValue的值摧莽。
解析
將常量池中的符號引用替換為直接引用,包括常量池中的CONSTANT_Class_info顿痪、CONSTANT_Fieldref_info镊辕、CONSTANT_Methodref_info等
詳見《深入理解虛擬機》P221
初始化
執(zhí)行<clinit>()方法
- <clinit>()方法內容來自于類變量的復制動作和static塊,按聲明順序
- <clinit>()方法與構造函數不同蚁袭,不需要顯示調用父類構造器征懈,虛擬機保證父類的<clinit>()方法已經執(zhí)行
- <clinit>()不是必須的
- 接口沒有初始化塊,但是可以有賦值
- 虛擬機保證一個類的<clinit>()方法在多線程環(huán)境下能被正確地加鎖同步揩悄,如果多線程同時去初始化一個類卖哎,那么只有一個線程會執(zhí)行類的<clinit>()方法,其他線程會阻塞删性。同一個類加載器下棉饶,一個類只會初始化一次。
類加載器
主要是加載階段的“通過一個類的全限定名獲取此類的二進制字節(jié)流”
用于類層次劃分镇匀,OSGi照藻,熱部署,代碼加密等領域
類與類加載器
任意一個類都要由加載它的類加載器和類本身一同確立它在Java虛擬機中的唯一性汗侵,也就是每個類加載器都有自己獨立的類名稱空間幸缕。
雙親委派模型
- 啟動類加載器(Bootstrap ClassLoader):
<JAVA_HOME>\lib和-Xbootclasspath - 擴展類加載器(Extension ClassLoader):
<JAVA_HOME>\lib\ext或者java.ext.dirs系統(tǒng)變量指定的類庫 - 應用程序加載器(Application ClassLoader):
ClassPath - 自定義類加載器:
父類加載器為應用程序加載器,通過組合實現
好處:
Java類隨著類加載器有了層級關系
摘抄自《深入理解Java虛擬機》