淺談Java類型裝載、連接與初始化

類型裝載、連接與初始化

Java虛擬機通過裝載、連接和初始化一個Java類型,使該類型可以被正在運行的Java程序所使用。其中裝載就是把二進制形式的Java class文件讀入Java虛擬機中去;連接把讀入虛擬機的二進制形式的Java class文件合併到虛擬機的運行時的狀態中去。連接可以分爲三個子步驟,驗證,準備和初始化。驗證步驟確保了Java類型數據格式正確並且適於Java虛擬機使用。準備爲該類型分配內存(如爲類變量分配內存)。解析負責將常量池中的符合引用轉換爲直接引用,虛擬機的實現可以推遲解析這一步驟。即當運行中的程序真正使用某個符號的時候再去解析它。(符號引用變爲直接引用)。初始化爲類變量賦以適當的初始值。
下面就這5個步驟進行詳細的解析:

裝載

裝載階段由三個動作完成

  1. 通過該類型的完全限定名,產生一個代表該類型的二進制數據流。
    該二進制數據流可以遵循class文件格式,也可以遵守其他數據格式。所有虛擬機必須能識別Java class文件格式,但是個別的也可以識別其他二進制格式。
    Java虛擬機並未規定該二進制數據流如何產生,因此下面列出了可能產生二進制數據的方式
    從本地文件系統裝載class文件;
    從網絡下載一個class文件;
    從ZIP,JAR,CAB中提取class文件;
    從專有數據庫中提取class文件;
    將Java源文件編譯爲class文件;
    使用上述方法,但是產生非class文件格式的二進制文件格式
  2. 解析這個二進制數據流爲方法區的內部數據結構。
  3. 創建一個表示該類型的java.lang.class類的實例。

裝載的最終目的就是創建java.lang.class的實例對象,解析二進制數據流爲方法區的內部數據結構,並在堆上面建立一個Class對象。Class對象是Java程序與內部數據結構之間的接口。

Java類型要麼被啓動類裝載器裝載,要麼被用戶自定義的類加載器加載。
類加載器並不是在某個類型首次主動使用的時候加載它。可以提前預判,預加載這個類型。如果預加載的時候出現問題,不會立即報錯。直到程序主動使用該類才報錯。也就是說程序已知不主動使用該類類型,類加載器就一直不報錯。

裝載時會做兩項檢查,雖然在驗證階段之前進行,但邏輯上屬於驗證階段。

  1. 驗證class文件格式是否正確,如魔數,每個部分在正確位置,正確的長度,文件的長度不是太長或太短等。 保證正式解析二進制class文件時候,不會造成虛擬機的崩潰。
  2. 確保每個除Object之外每個類都有一個超類,加載某個類的時候,確保該類的所有超類必須加載。

驗證

類型被加載後,虛擬機開始連接,連接第一步是驗證,即確認它是否符合Java語言的語義,並且不會危及虛擬機的完整性。
驗證的時機和方式,不同虛擬機有着不同的實現。虛擬機規範只是規定了虛擬機在驗證過程中可以拋出的異常以及在何種情況下必須拋出它們。
驗證階段往往會驗證如下內容:

  1. 檢查final的類不擁有子類
  2. 檢查final方法不能被覆蓋
  3. 確保類型和超類型之間沒有不兼容的方法申明(比如兩個方法簽名完全一樣,但返回類型不同)
  4. 檢查所有的常量池入口相互之間保持一致(比如CONSTANT_String_info入口的string_index項目必須是一個CONSTANT_Utf8_info入口的索引)
  5. 檢查常量池中所有的特殊字符串(類名,字段名,方法名,字段描述符和方法描述符)是否符合格式
  6. 檢查字節碼的完整性

上述任務最複雜的是最後一個,檢查字節碼的完整性。不能因爲挑戰指令超出了方法末尾導致了虛擬機崩潰。虛擬機必須在驗證階段檢查出這樣的字節碼是非法的,從而拋出一個錯誤。
不過虛擬機規範並未規定字節碼驗證必須在驗證階段執行,也可以在執行每條語句的時候單獨進行驗證。Java虛擬機指令集當初設定目標是字節碼流一次性使用數據流分析器進行驗證。不過運行時候進行驗證,會使得Java程序運行速度提高。當使用數據流分析器進行字節碼驗證時候,可能爲了確保符合Java語言的語義而裝載其他的類。比如加載了Float類,必須還加載其父類Number保證其不是一個final類。

驗證階段之後可能還會進行一項檢查,那就是符號引用的驗證。虛擬機在將符號引用替換爲直接引用,需要檢查該元素的存在性以及訪問的權限。邏輯上屬於驗證,但往往在解析階段發生。

準備

在準備階段,Java虛擬機爲類變量分配內存,並設置默認的初始值。在正式初始化階段之前,類變量都沒有被正式初始化爲真正的初始值。
準備階段,乾的另外一件事就是可能爲一些數據結構分配內存,進而提高程序運行的性能。比如方法表的建立,它包含了指向類中每一個方法包括從超類繼承方法的指針。從而可以在執行繼承方法時候不需要搜索超類。

解析

解析過程就是在類型的常量池中尋找類、接口、自動和方法的符合引用,把這些符號引用變成直接引用的過程。還記得邏輯上屬於驗證的符合引用的驗證麼?解析什麼時候執行是虛擬機自己決定的,可以在初始化階段後面執行。

初始化

初始化階段爲類變量賦予正確的初始值。在準備階段,類變量被賦予了默認值,在這一階段,類變量被賦予正確的初始值,即程序員希望這個類變量具備的起始值。
具體的初始化過程參見下一篇博客《淺談類加載的初始化階段

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章