1. 程式人生 > >深入理解JVM(六)——類加載器原理

深入理解JVM(六)——類加載器原理

區域 (六) HR tcl parse cep 引用關系 throws wid

我們知道我們編寫的java代碼,會經過編譯器編譯成字節碼文件(class文件),再把字節碼文件裝載到JVM中,映射到各個內存區域中,我們的程序就可以在內存中運行了。那麽字節碼文件是怎樣裝載到JVM中的呢?中間經過了哪些步驟?常說的雙親委派模式又是怎麽回事?本文主要搞清楚這些問題。

類裝載流程

技術分享圖片

1、加載

加載是類裝載的第一步,首先通過class文件的路徑讀取到二進制流,並解析二進制流將裏面的元數據(類型、常量等)載入到方法區,在java堆中生成對應的java.lang.Class對象。

2、連接

連接過程又分為3步,驗證、準備、解析

2.1、驗證

驗證的主要目的就是判斷class文件的合法性,比如class文件一定是以0xCAFEBABE開頭的,另外對版本號也會做驗證,例如如果使用java1.8編譯後的class文件要再java1.6虛擬機上運行,因為版本問題就會驗證不通過。除此之外還會對元數據、字節碼進行驗證,具體的驗證過程就復雜的多了,可以專門查看相關資料去了解。

2.2、準備

準備過程就是分配內存,給類的一些字段設置初始值,例如:

public static int v=1;

這段代碼在準備階段v的值就會被初始化為0,只有到後面類初始化階段時才會被設置為1。

但是對於static final(常量),在準備階段就會被設置成指定的值,例如:

public static final int v=1;

這段代碼在準備階段v的值就是1。

2.3、解析

解析過程就是將符號引用替換為直接引用,例如某個類繼承java.lang.object,原來的符號引用記錄的是“java.lang.object”這個符號,憑借這個符號並不能找到java.lang.object這個對象在哪裏?而直接引用就是要找到java.lang.object所在的內存地址,建立直接引用關系,這樣就方便查詢到具體對象。

3、初始化

初始化過程,主要包括執行類構造方法、static變量賦值語句,staic{}語句塊,需要註意的是如果一個子類進行初始化,那麽它會事先初始化其父類,保證父類在子類之前被初始化。所以其實在java中初始化一個類,那麽必然是先初始化java.lang.Object,因為所有的java類都繼承自java.lang.Object。

說完了類加載過程,我們來介紹一下這個過程當中的主角:類加載器。

類加載器

類加載器ClassLoader,它是一個抽象類,ClassLoader的具體實例負責把java字節碼讀取到JVM當中,ClassLoader還可以定制以滿足不同字節碼流的加載方式,比如從網絡加載、從文件加載。ClassLoader的負責整個類裝載流程中的“加載”階段。

ClassLoader的重要方法:

   1:  public Class<?> loadClass(String name) throws ClassNotFoundException

載入並返回一個類。

   1:  protected final Class<?> defineClass(byte[] b, int off, int len)

定義一個類,該方法不公開被調用。

   1:  protected Class<?> findClass(String name) throws ClassNotFoundException

查找類,loadClass的回調方法

   1:  protected final Class<?> findLoadedClass(String name)

查找已經加載的類。

系統中的ClassLoader

BootStrap Classloader (啟動ClassLoader)

Extension ClassLoader (擴展ClassLoader)

App ClassLoader(應用 ClassLoader)

Custom ClassLoader(自定義ClassLoader)

每個ClassLoader都有另外一個ClassLoader作為父ClassLoader,BootStrap Classloader除外,它沒有父Classloader。

ClassLoader加載機制如下:

技術分享圖片

自下向上檢查類是否被加載,一般情況下,首先從App ClassLoader中調用findLoadedClass方法查看是否已經加載,如果沒有加載,則會交給父類,Extension ClassLoader去查看是否加載,還沒加載,則再調用其父類,BootstrapClassLoader查看是否已經加載,如果仍然沒有,自頂向下嘗試加載類,那麽從 Bootstrap ClassLoader到 App ClassLoader依次嘗試加載。

值得註意的是即使兩個類來源於相同的class文件,如果使用不同的類加載器加載,加載後的對象是完全不同的,這個不同反應在對象的 equals()、isAssignableFrom()、isInstance()等方法的返回結果,也包括了使用 instanceof 關鍵字對對象所屬關系的判定結果。

技術分享圖片

從代碼上可以看出,首先查看這個類是否被加載,如果沒有則調用父類的loadClass方法,直到BootstrapClassLoader(沒有父類),我們把這個過程叫做雙親模式,

雙親模式的問題

頂層ClassLoader,無法加載底層ClassLoader的類

Java框架(rt.jar)如何加載應用的類?

比如:javax.xml.parsers包中定義了xml解析的類接口
Service Provider Interface SPI 位於rt.jar
即接口在啟動ClassLoader中。
而SPI的實現類,在AppLoader。

這樣就無法用BootstrapClassLoader去加載SPI的實現類。

解決

JDK中提供了一個方法:

   1:  Thread. setContextClassLoader()

用以解決頂層ClassLoader無法訪問底層ClassLoader的類的問題;
基本思想是,在頂層ClassLoader中,傳入底層ClassLoader的實例。

雙親模式的破壞

雙親模式是默認的模式,但不是必須這麽做;
Tomcat的WebappClassLoader 就會先加載自己的Class,找不到再委托parent;
OSGi的ClassLoader形成網狀結構,根據需要自由加載Class。

小結

本文介紹了類加載的流程,以及ClassLoader工作機制,最後分析雙親模式的缺陷,以及如何彌補該缺陷,介紹了tomcat、OSGI如何自定義類加載流程。

深入理解JVM(六)——類加載器原理