Java類加載機制總結

做者:不洗碗工做室 - Markluxjava

出處:Marklux's Pub程序員

版權歸做者全部,轉載請註明出處bash

本部分整理自《深刻理解JVM虛擬機》網絡

類的生命週期與加載時機

  1. 類的生命週期數據結構

    一個類從被加載到虛擬機內存中開始,到被卸載出內存爲止,整個生命週期包括了 加載、驗證、準備、解析、初始化、使用和卸載7個階段。其中 驗證、準備、解析 3部分統稱爲連接,以下圖:spa

    整個順序並非徹底固定的,其中解析階段能夠在初始化以後再開始,這樣即可以實現Java的運行時綁定(動態綁定)機制。code

  2. 類的加載時機cdn

    JVM虛擬機規範並無對類的加載時機作出嚴格的要求,只規定了如下五種狀況須要馬上觸發類的初始化:對象

    • 遇到new,getstatic,putstatic和invokestatic這四個字節碼指令時,若是類沒有進行過初始化,則須要先觸發其初始化。
    • 使用反射機制對類進行調用的時候,若是類沒有進行過初始化,則須要先觸發其初始化。
    • 當初始化一個類時,若是其父類尚未進行過初始化,則須要先觸發其父類的初始化。
    • 虛擬機啓動時,用戶須要指定一個要執行的主類(包含main方法),此時會先初始化這個類
    • 使用JDK1.7的動態語言支持時,若是一個MethodHandle實例最後的解析結果包含REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,且這個方法句柄對應的類沒有初始化,則須要先對其進行初始化。

    其他條件下,能夠由JVM虛擬機自行決定什麼時候去加載一個類。blog

  3. 主動引用和被動引用

    上面五種條件也被稱爲對類的主動引用,除此以外其餘引用類的方式都不會觸發初始化,即類的被動引用,舉個例子:

    public class Father {
    	static {
    		System.out.println("father init.");
    	}
    	public static int val = 123;
    }
    
    public class Son extends Father {
    	static {
    		System.out.println("son init.");
    	}
    }
    複製代碼

    當咱們訪問Son.val時,會發現並無輸出son init.

    對於靜態字段,只有直接定義這個字段的類纔會被初始化,所以經過子類來引用父類的靜態字段,子類至關因而被動引用,也就不會被初始化了。

類的加載過程

下面簡單的介紹一下整個加載過程當中,每一個階段JVM都執行了什麼操做:

加載(Loading)

加載過程是Java的一大特色,類的來源能夠多種多樣,壓縮包、網絡字節流、運行時動態計算生成(reflect)等等...這也造就了Java語言強大的動態特性。

  1. 經過一個類的完整限定名來獲取定義此類的二進制字節流(注意,字節流的來源很是靈活)
  2. 將這個字節流所表明的靜態儲存結構轉換成爲方法區的運行時數據結構
  3. 在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口

驗證(Verification)

這一過程主要是爲了確保Class的字節流中包含的信息符合虛擬機標準,以避免形成破壞

  1. 文件格式驗證
  2. 元數據驗證
  3. 字節碼驗證,經過數據流和控制流分析肯定程序的語義是合法的
  4. 符號引用驗證,確保解析動做可以正常執行

準備(Preparation)

這一階段將會爲類變量分配內存並設置其初始值,注意此時進行內存分配的僅包括類變量(static修飾),而且初始值一般狀況下是數據類型的零值而不是設定值,以下例

public static int val = 123;
複製代碼

在這一階段變量val的賦值是0而不是123,由於此時還沒有執行任何Java方法,而對val複製的putstatic指令在初始化階段後纔會執行。

固然也有特殊狀況,以下

public static final int val = 123;
複製代碼

加上final關鍵字修飾後,Java編譯時會爲val生成ConstantValue屬性,這時準備階段就會根據設置將其值設置爲123。

解析(Resolution)

此階段虛擬機將常量池內的符號替換爲直接引用,主要包含如下動做:

  1. 類或接口的解析
  2. 字段解析
  3. 類方法解析
  4. 接口方法解析

初始化(Initialization)

這時類加載過程的最後一步,這部分開始真正的執行Java代碼,也就是說,這個階段能夠由程序員參與。

此階段其實就是執行類構造器<clinit>()方法的過程。

類加載器

類加載器(Class Loader)是Java虛擬機的一大創舉,它將「獲取類的二進制字節流」這個過程交給了開發人員本身去實現,只要編寫不一樣的Class Loader,應用程序自己就能夠用相應的方式來獲取本身須要的類。

類與加載器的關係

對於任意一個類,都須要由加載它的類加載器和這個類自己一同確立其在虛擬機中的惟一性。

通俗的講,就是即使同一個Class文件,被不一樣的類加載器加載以後,獲得也不是同一個「類」(equals方法返回false)。

雙親委派模型

從虛擬機角度講,只有兩種類加載器,一種是啓動類加載器(Bootstrap ClassLoader),在hotpot上使用C++實現,屬於虛擬機的一部分;另外一種則是全部其餘類的加載器,這些加載器是獨立於虛擬機的,由Java語言實現的,從開發者角度看,能夠分爲如下兩類:

  1. 擴展類加載器(Extension ClassLoader)

  2. 應用程序類加載器(Appliaction ClassLoader)

固然開發人員也能夠本身編寫類加載器,最終不一樣的類加載器之間的層次關係以下圖所示:

這就是Java中著名的雙親委派模型,它要求除了頂級的BootStrap加載器以外,其餘類加載器都必須有父類加載器,工做流程以下:

若是一個類加載器收到了類加載的請求,他首先不會本身去嘗試加載這個類,而是將這個請求委派給父類加載器去完成,只有當父加載器反饋本身沒法完成加載請求時,子加載器纔會本身去嘗試加載這個類。

這樣作的好處是,Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係。舉個例子,好比java.lang.Object這個類,不管哪一個類加載器加載時,最終都會委派給Bootstrap加載器去加載,這就保證了整個系統運行過程當中的Object都是同一個類。

不然,若是用戶本身編寫了一個java.lang.Object類,並放在程序的classpath中,最終系統將會出現多個不一樣的Object類,整個Java體系就變得一團混亂了。

相關文章
相關標籤/搜索