一.概述
1. 定義:虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的java類型。類加載和連接的過程都是在運行期間完成的。
二.類加載的時機
1. 類加載的生命周期:加載(Loading)-->驗證(Verification)-->准備(Preparation)-->解析(Resolution)-->初始化(Initialization)-->使用(Using)-->卸載(Unloading)
2. 加載:這有虛擬機自行決定。
3. 初始化階段:
a) 遇到new、getstatic、putstatic、invokestatic這4個字節碼指令時,如果類沒有進行過初始化,出發初始化操作。
b) 使用java.lang.reflect包的方法對類進行反射調用時。
c) 當初始化一個類的時候,如果發現其父類還沒有執行初始化則進行初始化。
d) 虛擬機啟動時用戶需要指定一個需要執行的主類,虛擬機首先初始化這個主類。
注意:接口與類的初始化規則在第三點不同,接口不要氣所有的父接口都進行初始化。
三.類加載的過程
1. 加載
a) 加載階段的工作
i. 通過一個類的全限定名來獲取定義此類的二進制字節流。
ii. 將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。
iii. 在java堆中生成一個代表這個類的java.lang.Class對象,做為方法區這些數據的訪問入口。
b) 加載階段完成之后二進制字節流就按照虛擬機所需的格式存儲在方區去中。
2. 驗證:這一階段的目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求。
a) 文件格式驗證:驗證字節流是否符合Class文件格式的規范,並且能被當前版本的虛擬機處理。
b) 元數據驗證:對字節碼描述的信息進行語義分析,以確保其描述的信息符合java語言規范的要求。
c) 字節碼驗證:這個階段的主要工作是進行數據流和控制流的分析。任務是確保被驗證類的方法在運行時不會做出危害虛擬機安全的行為。
d) 符號引用驗證:這一階段發生在虛擬機將符號引用轉換為直接引用的時候(解析階段),主要是對類自身以外的信息進行匹配性的校驗。目的是確保解析動作能夠正常執行。
3. 准備:准備階段是正式為變量分配內存並設置初始值,這些內存都將在方法區中進行分配,這里的變量僅包括類標量不包括實例變量。
4. 解析:解析是虛擬機將常量池的符號引用替換為直接引用的過程。
a) 符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任意形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機實現的內存布局無關,引用的目標並不一定已經加載到內存中。
b) 直接引用:直接引用可以是直接指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。直接飲用是與內存布局相關的。
c) 類或接口的解析
d) 字段的解析
e) 類方法解析
f) 接口方法解析
5. 初始化:是根據程序員制定的主觀計划區初始化變量和其他資源,或者可以從另外一個角度來表達:初始化階段是執行類構造器<clinit>()方法的過程。
四.類加載器
1. 類與類加載器
a) 對於任意一個類都需要由加載它的類加載器和這個類本身一同確立其在jvm中的唯一性。
2. 雙親委派模型:類加載器的雙親委派模型要求除了頂層的啟動類加載器外,其他的類都應有自己的父類加載器,這里類加載器之間的父子關系一般不會已繼承來實現而是實用組合關系來復用附加在代碼。
3. 雙親委派模型的工作過程:如果一個類加載器收到了類加載的請求,它首先不會自己嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的家在請求最終都應該傳到頂層的啟動類夾在其中,只有父類反饋自己無法完成這個加載請求時,父類才會嘗試自己去加載。
4. 有點:java隨他的類加載器一起具備了帶有優先級的層次關系。
5. 類加載器會首先代理給其它類加載器來嘗試加載某個類。這就意味着真正完成類的加載工作的類加載器和啟動這個加載過程的類加載器,有可能不是同一個。真正完成類的加載工作是通過調用 defineClass來實現的;而啟動類的加載過程是通過調用 loadClass來 實現的。前者稱為一個類的定義加載器(defining loader),后者稱為初始加載器(initiating loader)。在 Java 虛擬機判斷兩個類是否相同的時候,使用的是類的定義加載器。也就是說,哪個類加載器啟動類的加載過程並不重要,重要的是最終定義這個類的加載器。兩種類加 載器的關聯之處在於:一個類的定義加載器是它引用的其它類的初始加載器。
對於一個類加載器實例來說,相同全名的類只加載一次,即 loadClass方法不會被重復調用。
