虛擬機類加載機制

目錄

1. 什麼是類加載機制?

2. 什麼時候進行類加載?

3. 類加載時都幹了啥?

3.1 加載

3.2 驗證

3.3 準備

3.4 解析

3.5 初始化

4. 什麼是類加載器?

4.1 類與類加載器的關係

4.2 雙親委派模型


1. 什麼是類加載機制?

所謂類加載機制就是把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型。

在Java語言中,類型的加載、連接和初始化過程都是在程序運行期間完成的,這種策略雖然會另加載時稍微增加一些性能開銷,但是會爲Java應用程序提供高的靈活性。例如,編寫一個面向接口的應用程序,可以等到運行時再指定其實際的實現類。

2. 什麼時候進行類加載?

類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的整個生命週期包括:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個階段。

其中,加載、驗證、準備、初始化和卸載這5個階段的順序是確定的,類的加載過程必須按照這種順序按部就班的開始(注意是開始,不是串行),而解析階段則不一定:它在某些情況下可以在初始化階段會後再開始,這是爲了支持Java語言的運行時綁定(也稱爲動態綁定或者晚期綁定)。

什麼時候要開始第一個階段 - “加載”呢?

1)使用new關鍵字實例化對象的時候、讀取後者設置一個類的靜態字段(被final修飾、已在編譯期把結果放入常量池的靜態字段除外)的時候,以及調用一個類的靜態方法的時候;

2)使用反射(使用java.lang.reflect包的方法)對類就行反射調用的時候,如果類沒有進行初始化則需要先觸發其初始化;

3)當初始化一個類的時候,如果其父類還沒有進行初始化,則先要初始化其父類;

4)當虛擬機啓動時,用戶需要指定一個要執行的類(包含main()方法的那個),虛擬機會先初始化這個類;

5)當使用JDK 1.7的動態語言支持時,如果一個 java.lang.invoke.MethodHandle實例最後的解析結果是REF_getStatis、REF_putStatic、REF_invokeStatic的方法句柄,並且這個方法句柄對應的類還沒有進行初始化時,需要先將其初始化。

對於這5種觸發類進行初始化的場景,虛擬機規範中使用了一個很強烈的限定語:“有且僅有”,這5種場景中的行爲稱爲對一個類進行的主動引用。除此之外,所有引用類的方式都不會觸發初始化,稱爲被動引用。

常見的被動引用場景如下:

1)通過子類引用父類的靜態字段,不會導致子類初始化;

2)通過數組定義來引用類,不會觸發此類的初始化;

3)常量在編譯階段會存入調用類的常量池,本質上並沒有直接引用到定義常量的類,因此不會觸發定義常量的類的初始化。

3. 類加載時都幹了啥?

3.1 加載

“加載” 是 “類加載”(Class Loading)過程的一個階段。在加載階段,虛擬機需要完成以下3件事:

1)通過一個類的全限定名來獲取定義此類的二進制字節流;

2)將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構;

3)在內存中生成一個代表這個類的 java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口。

上述3點要求比較籠統,沒有對具體細節進行描述。比如第一條,沒有說這個二進制字節流要從哪獲取,怎樣獲取,所以在具體實現上就很靈活。比如,可以從ZIP包中獲取,從網絡中獲取,運行時計算生成(動態代理)、由其他文件生成、從數據庫中讀取等。

3.2 驗證

驗證是連接階段的第一步,主要目的是確保Class文件的字節流信息符合當前虛擬機的要求,不會危害虛擬機自身的安全。

1)文件格式驗證;

2)元數據驗證(對字節碼描述的信息進行語義分析)。是否有父類啊(所有的類都有父類)、是否繼承了不允許被繼承的類啊、是否實現了其父類的抽象方法啊(如果不是抽象類)、類中的字段、方法是否與父類矛盾啊等等;

3)字節碼驗證(驗證程序的語義是合法、符合邏輯的);

4)符號引用驗證。對類自身以外(常量池中的各種符號引用)的信息進行匹配性校驗。

3.3 準備

爲類變量(被static修飾的變量)分配內存並設置類變量初始值(通常指零值,用戶指定的值在初始化時設置)。這些變量所使用的內存都將在方法區中進行分配。

3.4 解析

將常量池內的符號引用替換爲直接引用。

3.5 初始化

真正開始執行類中定義的Java代碼(或者說字節碼)。

4. 什麼是類加載器?

虛擬機設計團隊把類加載階段中的“通過一個類的全限定名來獲取描述此類的二進制字節流”這個動作放到Java虛擬機外部去實現,以便讓應用程序自己決定如何去獲取所需要的類,實現這個動作的代碼模塊稱爲“類加載器”。

4.1 類與類加載器的關係

類加載器雖然只用於實現類的加載,但它的作用卻遠不限於此。對於任意一個類,都需要由它的類加載器和這個類本身一同確立其在Java虛擬機中的唯一性,換句話說,比較兩個類是否“相等”,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則即使這兩個類來源於同一個Class文件,被同一個虛擬機加載,只要加載它們的類加載器不同,它們就不是相等的類。

這裏所指的“相等”,包括代表類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果,也包括使用instanceof關鍵字做對象所屬關係判定等情況。

4.2 雙親委派模型

從Java虛擬機的角度講,只存在兩種不同的類加載:啓動類加載器(Bootstrap ClassLoader,C++實現)和所有其他的類加載器(Java實現,繼承自java.lang.ClassLoader)。

但從Java開發人員的角度看看,類加載器可以劃分的更細緻一些:

1)啓動類加載器(Bootstrap ClassLoader):加載<JAVA_HOME>\lib 目錄或被 -Xbootclasspath參數指定的路徑中的並且是虛擬機識別(僅按文件名識別,例如rt.jar,名字不符合的類庫即使放在lib目錄下也不會被加載)的類。啓動類無法被Java程序直接引用。

2)擴展類加載器(Extension ClassLoader):記載<JAVA_HOME>\lib\ext目錄或者被java.ext.dirs系統變量指定的路徑中的類。開發者可以直接使用擴展類加載器。

3)應用程序類加載器(Application ClassLoader):負責加載用戶類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這個類加載器。如果用戶沒有自定義過自己的類加載器,一般情況下這個就是程序默認的類加載器。

4)如果有必要,用戶可以加入自己定義的類加載器 -- 自定義加載器。

圖中展示的類加載器之間的這種層次關係被稱爲加載器的雙親委派模型。雙親委派模型要求除了頂層的啓動類加載器外,其他的類加載器都應有自己的父類加載器(組合的方式實現)。

雙親委派模型的工作過程:如果一個類加載器收到了加載類的要求。它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啓動類加載器中,只有當父加載器反饋自己也無法完成這個加載請求(它的搜索範圍沒有找到所需的類)時,子加載器纔會嘗試自己去加載。

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