java代碼,會通過編譯器編譯成字節碼文件(class文件),再把字節碼文件裝載到JVM中,映射到各個內存區域中,程序就能夠在內存中運行了。那麼字節碼文件是怎樣裝載到JVM中的呢?中間通過了哪些步驟?常說的雙親委派模式又是怎麼回事?本文主要搞清楚這些問題。java
一、加載tomcat
加載是類裝載的第一步,首先經過class文件的路徑讀取到二進制流,並解析二進制流將裏面的元數據(類型、常量等)載入到方法區,在java堆中生成對應的java.lang.Class對象。網絡
二、鏈接app
鏈接過程又分爲3步,驗證、準備、解析框架
2.一、驗證ide
驗證的主要目的就是判斷class文件的合法性,好比class文件必定是以0xCAFEBABE開頭的,另外對版本號也會作驗證,例如若是使用java1.8編譯後的class文件要再java1.6虛擬機上運行,由於版本問題就會驗證不經過。除此以外還會對元數據、字節碼進行驗證,具體的驗證過程就複雜的多了,能夠專門查看相關資料去了解。spa
2.二、準備code
準備過程就是分配內存,給類的一些字段設置初始值,例如:xml
public static int v=1;
這段代碼在準備階段v的值就會被初始化爲0,只有到後面類初始化階段時纔會被設置爲1。對象
可是對於static final(常量),在準備階段就會被設置成指定的值,例如:
public static final int v=1;
這段代碼在準備階段v的值就是1。
2.三、解析
解析過程就是將符號引用替換爲直接引用,例如某個類繼承java.lang.object,原來的符號引用記錄的是「java.lang.object」這個符號,憑藉這個符號並不能找到java.lang.object這個對象在哪裏?而直接引用就是要找到java.lang.object所在的內存地址,創建直接引用關係,這樣就方便查詢到具體對象。
三、初始化
初始化過程,主要包括執行類構造方法、static變量賦值語句,staic{}語句塊,須要注意的是若是一個子類進行初始化,那麼它會事先初始化其父類,保證父類在子類以前被初始化。因此其實在java中初始化一個類,那麼必然是先初始化java.lang.Object,由於全部的java類都繼承自java.lang.Object。
說完了類加載過程,來介紹一下這個過程中的主角:類加載器。
類加載器ClassLoader,它是一個抽象類,ClassLoader的具體實例負責把java字節碼讀取到JVM當中,ClassLoader還能夠定製以知足不一樣字節碼流的加載方式,好比從網絡加載、從文件加載。ClassLoader的負責整個類裝載流程中的「加載」階段。
ClassLoader的重要方法:
public Class<?> loadClass(String name) throws ClassNotFoundException //載入並返回一個類。
protected final Class<?> defineClass(byte[] b, int off, int len) //定義一個類,該方法不公開被調用。
protected Class<?> findClass(String name) throws ClassNotFoundException //查找類,loadClass的回調方法
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中提供了一個方法:
Thread. setContextClassLoader()
用以解決頂層ClassLoader沒法訪問底層ClassLoader的類的問題;
基本思想是,在頂層ClassLoader中,傳入底層ClassLoader的實例。
雙親模式是默認的模式,但不是必須這麼作;
Tomcat的WebappClassLoader 就會先加載本身的Class,找不到再委託parent;
OSGi的ClassLoader造成網狀結構,根據須要自由加載Class。
本文介紹了類加載的流程,以及ClassLoader工做機制,最後分析雙親模式的缺陷,以及如何彌補該缺陷,介紹了tomcat、OSGI如何自定義類加載流程。
參考資料:
《實戰Java虛擬機》 葛一鳴
《深刻理解Java虛擬機(第2版)》 周志明