一、類加載的時機
類的生命週期,如下
何時需要進行類加載:
-
遇到new、getstatic、putstatic、invokestatic字節碼指令。
-
使用java.lang.reflect包方法對類反射調用。
-
初始化一個類,其父類未初始化時。
-
虛擬機啓動時,需要執行一個main方法,那麼需要初始化該類。
-
jdk1.7動態語言支持時,當java.lang.invoke.MethodHandle實例最後的解析結果爲REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,且該句柄所對應的類沒有初始化,那麼需要先觸發其初始化。
二、類的加載過程
1)加載(Class Loading)
-
通過類全限定名獲取定義此類的二進制字節流。
-
將字節流代表的靜態存儲結構轉化爲方法區的運行時數據結構。
-
在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口。
數組的加載:數組類本身不通過類加載器創建,是由JVM直接創建。
-
數組的組件類型(Component Type)是引用類型,那就加載改組件類型,數組將在加載該組件類型的類加載器的類名稱空間上被標識。
-
數組的組件類型不是引用類型,JVM將會把數組標記爲與引導類加載器關聯。
-
數組類的可見性與它的組件類型的可見性一致,如果組件類型不是引用類型,那數組的類的可見性默認爲public。
2)驗證
-
文件格式驗證:魔數、版本、常量池中的常量類型、編碼格式...
-
元數據驗證:是否有父類、父類是否允許繼承、非抽象類是否實現了所有接口中的方法、字段方法是否與父類有衝突...
-
字節碼校驗:操作數棧的數據類型與指令代碼序列不衝突、跳轉指令不會跳轉到方法體以外的字節碼指令上、方法體中的類型轉換是有效的...
-
符號引用驗證:發生在符號引用轉爲直接引用時(解析),符號引用中的字符串描述的全限定名是否能找到對應的類、是否能找到符合描述的方法與字段、符號引用的類、字段、方法是否可被當前類訪問(private、public等等)
3)準備
爲類變量分配內存,並設置初始值。static 變量
4)解析
將常量池的符號引用替換爲直接引用,包含:類或接口解析、字段解析、類方法解析、接口方法解析
5)初始化
執行<clinit>,初始化類變量,執行靜態代碼塊。
三、類加載器
類與類加載器
對於任何一個類,都需要有加載它的類加載器與這個類本身確定其在JVM中的唯一性。
雙親委派模型
-
啓動類加載器:加載JAVA_HOME/lib下的類,或者-Xbootclasspath指定路徑的類。
-
擴展類加載器:加載JAVA_HOME/lib/ext下的類,或者被java.ext.dirs系統變量指定路徑的類。
-
應用類加載器:加載用戶路徑(ClassPath)上所制定的類。
雙親委派:類加載器收到類加載請求時,先委派給父類加載器去加載,每一個類加載器都是如此,當父類加載器加載了,則加載完成,若父類無法加載,則再由子加載器嘗試加載。
破壞雙親委派模型
使用線程上下文類加載器(Thread Context ClassLoader),可以通過java.lang.Thread的setContextClassLoader()進行設置,在JVM中會把當前線程的類加載器加載不到的類交給線程上下文類加載器來加載。