JVM規范系列第5章:加載、鏈接與初始化


加載是根據特定名稱查找類或接口類型的二進制表示(Binary Representation),並由此二進制表示創建類或接口的過程。

加載,就是指去尋找類或接口的過程。

鏈接是為了讓類或接口可以被 Java 虛擬機執行,而將類或接口並入虛擬機運行時狀態的過程。

鏈接,就是將類或接口與JVM鏈接起來的過程。

類或接口的初始化是指執行類或接口的初始化方法 (§ 2.9)

初始化,就是執行 方法的過程。

Java 虛擬機為每個類型都維護一個常量池。

這里注意關鍵詞「每個類型」,也就是說整型是一個常量池,字符串類型也是一個常量池。

運行時常量池中的所有引用最初都是符號引用。

符號引用的意思是它只是一個符號,需要后續通過鏈接,替換為具體的內存地址。

這里說的符號引用,下面列舉幾種:

  • CONSTANT_Fieldref_info 類或接口的某個字段的符號引用
  • CONSTANT_Methodref_info 類中某個方法的符號引用
  • CONSTANT_InterfaceMethodref_info 接口的某個方法的符號引用
  • CONSTANT_MethodHandle_info 方法句柄的符號引用
  • 等等

創建和加載

Java 虛擬機的啟動是通過引導類加載器(Bootstrap Class Loader § 5.3.1) 創建一個初始類(Initial Class)來完成,這個類是由虛擬機的具體實現指定。緊接着, Java 虛擬機鏈接這個初始類,初始化並調用它的 public void main(String[])方法。之后的整個執行過程都是由對此方法的調用開始。執行 main 方法中的 Java 虛擬機指令可能會導致 Java 虛擬機鏈接另外的一些類或接口,也可能會調用另外的方法。

簡單地說,虛擬機通過鏈接初始類,由此會調用其他類或接口,從而開始整個龐大Java項目的運行。

首先,Java 虛擬機檢查引導類加載器是否是已加載過的標記為 N 的類或接口的初始加載器。如果是的話,這個類或接口就是 C,並且不再創建其它類型。否則, Java 虛擬機將參數 N 傳遞給引導類加載器的特定方法,以平台相關的方式搜索 C 的描述。典型的情況是,類或文件會被表示為樹型文件系統中的某個文件,類或接口的名稱就是此文件的路徑名。

這段話描述了引導類加載器如何加載類或接口,可以詳細看看。

首先, Java 虛擬機檢查 L 是否為已經加載過的標識為 N 的類或接口的初始加載器。如果是的話,那個類或接口就是 C,不用再創建其它類了。否則 Java 虛擬機會調用 L 的 loadClass(N)①方法。 這次調用的返回值就是創建好的類或接口 C。 Java 虛擬機會記錄下 L 是 C 的初始加載器(§ 5.3.4)。這節其余的部分會更詳細地描述這個過程。

這段話描述了自定義類加載器如何加載類或接口。

鏈接

類加載器需要特別考慮到類型的安全鏈接問題。一種可能出現的情況是,當兩個不同的類加載器初始加載標記為 N 的類或接口時,在每個加載器里 N 表示着不同的類或接口。

這里的意思或許是說,一個同樣的類被加載在不同的類加載器中,其表示兩個完全不同的類。即使這些類或接口的字節碼完全相同。

《Java 虛擬機規范》 允許靈活地選擇鏈接(並且會有遞歸加載)發生的時機。

鏈接過程可以靈活選擇。

例如,Java 虛擬機實現可以選擇只有在使用類或接口中符號引用時才去逐一解析它(延遲解析),或是當類在驗證時就解析每個引用(預先解析)。這意味着在一些虛擬機實現中,在類或接口被初始化動作開始后,解析動作可能還正在進行。

解析過程可以延遲解析,也可以預先解析。

驗證(Verification, § 4.10)階段用於確保類或接口的二進制表示結構上是正確的。驗證過程可能會導致某些額外的類和接口被加載進來(§ 5.3),但不應該會導致它們也需要驗證或准備。

驗證會導致其他類的加載但不會導致它們也需要驗證或准備。

准備(Preparation)階段的任務是為類或接口的靜態字段分配空間,並用默認值初始化這些字段(§ 2.3, § 2.4)。這個階段不會執行任何的虛擬機字節碼指令。

准備階段是為類或接口的靜態字段分配空間,並用默認值初始化這些字段。注意,並不會執行任何虛擬機字節碼指令。

Java 虛擬機指令 anewarray、 checkcast、 getfield、 getstatic、 instanceof、nvokedynamic、 invokeinterface、 invokespecial、 invokestatic、 invokevirtual、ldc、 ldc_w、 multianewarray、 new、 putfield 和 putstatic將符號引用指向運行時常量池。執行上述任何一條指令都需要對它的符號引用的進行解析。

解析就是解析符號引用的過程,將其轉為具體的值。

解析(Resolution)是根據運行時常量池的符號引用來動態決定具體的值的過程。

接下來的大部分內容,都是對於特定內容的解析步驟,例如:對於類或接口解析、字段解析、普通方法解析、接口方法解析、調用點限定符解析等。這部分內容確實晦澀難懂,建議大致通讀一遍就好,暫時不要深究。

初始化

初始化(Initialization) 對於類或接口來說,就是執行它的初始化方法(§ 2.9)。

這里就是開始執行初始化方法了,包括 兩個初始化方法。具體有5種情況下會觸發這種初始化。

1、在執行下列需要引用類或接口的 Java 虛擬機指令時: new, getstatic, putstatic或 invokestatic。這些指令通過字段或方法引用來直接或間接地引用其它類。執行上面所述的 new 指令,在類或接口沒有被初始化過時就初始化它。執行上面的 getstatic,
putstatic 或 invokestatic 指令時,那些解析好的字段或方法中的類或接口如果還沒有被初始化那就初始化它。
2、在初次調用 java.lang.invoke.MethodHandle 實例時,它的執行結果為通過 Java虛擬機解析出類型是 2(REF_getStatic)、 4(REF_putStatic)或者 6(REF_invokeStatic)的方法句柄(§ 5.4.3.5)。
3、在調用 JDK 核心類庫中的反射方法時,例如, Class 類或 java.lang.reflect 包。
4、在對於類的某個子類的初始化時。
5、在它被選定為 Java 虛擬機啟動時的初始類(§ 5.2) 時。

JVM規范系列文章目錄


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM