JVM 將字節碼二進制流加載到內存稱為類加載。
什么時候加載類
- new 實例化對象。而對象所屬類還沒被加載。
- 讀取/設置類的靜態非常量字段,常量字段在常量池。
- 調用類的靜態方法。
- 類初始化,優先初始化父類。
- 虛擬機啟動時,先加載用戶指定的主類。
第一步:加載
JVM 需要完成三項工作:
- 通過類的全限定類型獲取二進制字節流。
- 將字節流轉化為方法區的運行時數據結構。
- 生成一個Class對象,作為數據訪問入口。
數組類由其組件類型定義的加載器加載。如果是基本類型數組,由引導類加載器加載,且默認訪問權限為 public。
第二步:驗證
目的是檢驗字節碼二進制字節流是否符合虛擬機規范,避免其威脅虛擬機安全。包括:
- 文件格式驗證:驗證是否符合Class文件格式。是否存在魔數,主次版本號,常量等。
- 元數據驗證:驗證是否符合 Java 語言規范。是否存在父類,是否符合訪問權限,是否符合重載/重寫規范等。
- 字節碼驗證:驗證方法體語義。
- 符號引用驗證:是否可以將符號引用轉化為直接引用。
第三步:準備
為類的靜態變量分配內存并設置初始值。JDK8 以后,類變量在 Class 對象里,Class 對象在堆中。如果是變量,初始值是零值,如果是常量,初始值就是字面量。
第四步:解析
將符號引用轉化為直接引用。比如全限定類名com.example.demo.Hello()
就是符號引用。直接引用就是內存中目標指針,句柄或者相對偏移量。包括類/接口/字段/方法、接口方法解析。
第五步:初始化
執行類的靜態賦值語句以及靜態代碼塊。
public class Demo {public static int A = 3;static {A = 2;}
}
類加載器
JVM 中類加載器不僅起到加載類的作用。還起到唯一定義作用。加載器與類共同確定類的唯一性。不同加載器加載的同一個類不相等,Object.equals()
, isAssignableFrom()
, isInstance()
, instanceof
等判斷都為false。
雙親委派模型
從 JVM 的角度,只存在兩種加載器,一種是啟動類加載器,它由 C++ 實現,無法獲取其實例對象。一種是 Java 類加載器java.lang.ClassLoader
,用戶可以獲取實例。
從開發人員的角度,類加載器分為三種。啟動類加載器,擴展類加載器和系統類加載器。啟動類加載器是同一個東西。擴展類加載器和系統類加載器是 Java 類加載器的實例。擴展類加載器加載 Java 的系統類庫。系統類加載器加載用戶類路徑上的所有類庫。
雙親委派模型指除了啟動類加載器,其余類加載器都有父類加載器。工作原理是:類加載器首先將加載請求委派給父類加載器,遞歸委派到啟動類加載器。父類加載器反饋無法加載,子類加載器才自己加載對象。
它的優點是:Java 的類也具有層次關系。無論哪個類加載 Object 類,最終都會交給啟動類加載器。