每日一問:講講 JVM 的類載入機制

南塵發表於2019-06-17

前面給大家講解了 Java 虛擬的記憶體結構 以及 Java 虛擬機器的垃圾回收機制,我們更加明白了 Java 的記憶體管理機制,今天我們來講講 Java 虛擬機器的另外一個高頻考點:類載入機制。

JVM 的類載入過程分為載入、驗證、準備、解析、初始化 5 個階段。

載入

載入階段由類載入器進行負責,類載入器根據一個類的全限定名讀取該類的二進位制位元組流到 JVM 內部,然後轉換為一個對應的 java.lang.Class 物件例項;一個類由類載入器和類本身一起確定,所以不同類載入器載入同一個類得到的 java.lang.Class 也是不同的。

驗證

驗證階段負責驗證類資料資訊是否符合 JVM 規範,是否是一個有效的位元組碼檔案。

準備

準備階段是正式為類變數(static 修飾的變數)分配記憶體並設定類變數初始值的階段,這些記憶體都將在方法區進行分配。

這個階段由兩個容易產生混淆的概念需要強調一下,首先是這時候進行記憶體分配的僅包括類變數,而不包括例項變數,例項變數將會在物件例項化時隨著物件一起分配在 Java 堆中。其次這裡所說的初始值在沒有被 final 修飾的時候都是資料型別的零值,只有類似 public static final int value = 1024 這樣的情況下才回直接被賦值 123。

解析

解析階段是虛擬機器將常量池內的「符號引用」替換為「直接引用」的過程。

初始化

初始化階段負責將所有的 static 域按照程式指定操作對應執行(賦值 static 變數,執行 static 塊)。

上述階段通常都是交叉混合允許,沒有嚴格的先後執行順序。

雙親委派模型

站在 Java 虛擬機器的角度講,只存在兩種不同的類載入器:一種是啟動類載入器,這個類載入器使用 C++ 語言實現,是虛擬機器自身的一部分,另外一種是所有其他的類載入器,這種類載入器都由 Java 語言實現,獨立於虛擬機器外部,並且全部繼承自抽象類 java.lang.ClassLoader

雙親委派模型並不是一個強制性的約束模型,而是 Java 設計者們推薦給開發者們的類載入器實現方式。它的工作過程是:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,每一個層次的類載入器都是如此,因此所有的載入請求最終都應該傳送到頂層的類載入器中,只有當父載入器反饋自己完成這個載入請求的時候,子載入器才會嘗試自己去載入。

使用雙親委派模型來組織類載入器之間的關係,有一個顯而易見的好處就是 Java 類隨著它的類載入器一起具備了一種帶有優先順序的層次關係。

採用雙親委派模型的原因

比如黑客定義一個 java.lang.String 類,該 String 類和系統 String 類有一樣的功能,只是在某個方法比如 equels() 中加入了病毒程式碼,並且通過自定義類載入器加入 JVM 中,如果沒有雙親委派模型,那麼 JVM 就可能誤以為黑客編寫的 String 類是系統 String 類,導致「病毒程式碼」最終被執行。而有了雙親委派模型,黑客定義的 java.lang.String 類就用於不會被載入進記憶體,因為最頂端的類載入器會載入系統的 String 類,最終自定義的類載入器無法載入 java.lang.String 類。

可以通過重寫 loadClass() 方法,打破雙親委派模型。

最近的知識比較枯燥,但還是我們所必須瞭解的。

參考文獻:《深入理解 Java 虛擬機器》

相關文章