ClassLoader的具體做用就是將class文件加載到jvm虛擬機中去,程序就能夠正確運行了。可是,jvm啓動的時候,並不會一次性加載全部的class文件,而是根據須要去動態加載。java
class文件是字節碼格式文件。程序員
Dalvik虛擬機如同其餘Java虛擬機同樣,在運行程序時首先須要將對應的類加載到內存中。而在Java標準的虛擬機中,類加載能夠從class文件中讀取,也能夠是其餘形式的二進制流。所以,咱們經常利用這一點,在程序運行時手動加載Class,從而達到代碼動態加載執行的目的。緩存
JVM(虛擬機)把描述類的數據的字節碼.Class文件加載到內存,並對數據進行校訂、轉換解析和初始化,最終造成能夠被虛擬機直接使用的java類型,這就是虛擬機的類加載機制。安全
類從被加載到虛擬機內存中開始,到卸載出內存爲止,它的生命週期包括以下七個階段,其中驗證、準備、解析三個部分統稱爲連接:數據結構
其中加載、連接(驗證、準備、解析)、初始化爲類的加載過程。下面咱們來介紹一下類加載的每一步。jvm
一、加載(查找並加載類的二進制數據):函數
加載階段是「類加載機制」中的一個階段,這個階段一般也被稱做「裝載」,主要完成以下工做:佈局
(1)經過「類全名」來獲取定義此類的二進制字節流優化
(2)將字節流所表明的靜態存儲結構轉換爲方法區的運行時數據結構spa
(3)在java堆中生成一個表明這個類的java.lang.Class對象,做爲方法區這些數據的訪問入口
相對於類加載過程的其餘階段,加載階段(準確的說,是加載階段中獲取二進制字節流的動做)是開發期可控性最強的階段,
由於加載階段可使用系統提供的類加載器(ClassLoader)來完成,也能夠由用戶自定義的類加載器完成,開發人員能夠經過定義本身的類加載器去控制字節流的獲取方式。
加載階段完成後,虛擬機外部的二進制字節流就按照虛擬機所需的格式存儲在方法區之中,方法區中的數據存儲格式由虛擬機實現自行定義。
虛擬機並未規定此區域的具體數據結構。而後在java堆中實例化一個java.lang.Class類的對象,這個對象做爲程序訪問方法區中的這些類型數據的外部接口。以下圖:
二、驗證(確保被加載類的正確性):
驗證是連接階段的第一步,這一步主要目的是確保class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身安全。
驗證階段主要包含以下四個檢驗過程:
(1)文件格式驗證:驗證class文件格式規範。
(2)元數據驗證:這個階段是對字節碼描述的信息進行語義分析,以保證描述的信息符合java語言規範要求。
(3)字節驗證碼:進行數據流和控制流分析,這個階段對類的方法體進行校驗分析,這個階段的任務是保證被校驗類的方法在運行時不會作出危害虛擬機安全的行爲。
(4)符號引用驗證:符號引用中經過字符串描述的全限定名是否能找到對應的類、符號引用類中的類,字段和方法的訪問性(private、protected、public、default)是否可被當前類訪問。
三、準備(爲類的靜態變量分配內存,並將其初始化爲默認值):
準備階段是正式爲類變量分配內存並設置類變量初始值的階段,這些內存都將在方法區中進行分配。注意下面兩點:
(1)這時候進行內存分配的僅包括類變量(static修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨着對象一塊兒分配在java堆中。
(2)這裏所說的初始值「一般狀況」下是數據類型的零值。好比:public static int value = 12;那麼變量value在準備階段事後的初始值爲0而不是12,由於這時候還沒有開始執行任何java方法,而把value賦值爲12的動做將在初始化階段纔會被執行。
四、解析(把類中的符號引用轉換爲直接引用):
解析階段是虛擬機常量池內的符號引用替換爲直接引用的過程。
符號引用:符號引用是一組符號來描述所引用的目標對象,符號能夠是任何形式的字面量,只要使用時能無歧義地定位到目標便可。符號引用與虛擬機實現的內存佈局無關,引用的目標對象並不必定已經加載到內存中。
直接引用:直接引用能夠是直接指向目標對象的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是與虛擬機內存佈局實現相關的,同一個符號引用在不一樣的虛擬機實例上翻譯出來的直接引用通常不會相同,若是有了直接引用,那引用的目標一定已經在內存中存在。
五、初始化(爲類的靜態變量賦予正確的初始值):
類的初始化階段是類加載過程的最後一步,在準備階段,類變量已賦過一次系統要求的初始值,而在初始化階段,則是根據程序員經過程序制定的主觀計劃去初始化類變量和其餘資源,或者能夠從另一個角度來表達:初始化階段是執行類構造器<clinit>()方法的過程。在如下四種狀況下初始化過程會被觸發執行:
(1)遇到new、getstatic、putstatic或invokestatic這四個字節碼指令時,若是類沒有進行過初始化,則需先觸發其初始化。
(2)使用java.lang.reflect包的方法對類進行反射調用的時候。
(3)當初始化一個類的時候,若是發現其父類尚未進行過初始化、則須要先觸發其父類的初始化。
(4)jvm啓動時,用戶指定一個執行的主類(包含main方法的那個類),虛擬機會先初始化這個類。
JVM設計者把類加載階段中的「經過‘類全名’來獲取定義此類的二進制字節流」這個動做放到Java虛擬機外部去實現,以便讓應用程序本身決定如何去獲取所須要的類。實現這個動做的代碼模塊稱爲「類加載器」。
一、類與類加載器
對於任何一個類,對須要由加載它的類加載器和這個類來確立其在JVM中的惟一性。也就是說,兩個類來源於同一個Class文件,而且被同一個類加載器加載,這兩個類才相等。
二、JVM三種預約義類型類加載器
當一個JVM啓動的時候,Java缺省開始使用以下三種類型類加載器:
(1)啓動類加載器(Bootstrap ClassLoader):負責加載JAVA_HOME\lib目錄中而且能被虛擬機識別的類庫到JVM內存中,若是名稱不符合的類庫即便放在lib目錄中也不會被加載。該類加載器沒法被Java程序直接引用。
(2)擴展類加載器(Extension ClassLoader):該加載器主要是負責加載JAVA_HOME\lib\,該加載器均可以被開發者直接使用。
(3)應用程序類加載器(Application ClassLoader):該類加載器也成爲系統類加載器,它負責加載用戶類路徑(Classpath)上所指定的類庫,開發者能夠直接使用該類加載器,若是應用程序中沒有自定義過本身的類加載器,通常狀況下這個就是程序中默認的類加載器。
對於上面三種類加載器,其中啓動類加載器(Bootstrap ClassLoader)使用C++語言實現,屬於虛擬機自身的一部分,不是ClassLoader的子類。全部其餘的類加載器,這些類加載器是由Java語言實現,獨立於JVM外部,而且所有繼承自抽象類lava.lang.ClassLoader。
三、雙親委派模型
應用程序都是經過上面的三種類加載器相互配合進行加載的,咱們也能夠加入本身定義的類加載器。這些類加載器的關係以及工做過程以下:
如上圖所示類加載器之間的這種層次關係,就稱爲類加載器的雙親委派模型。該模型要求除了頂層的啓動類加載器外,其他的類加載器都應當有本身的父類加載器。子類加載器和父類加載器不是以繼承的關係來實現,而是經過組合關係來複用父加載器的代碼。
雙親委派模型的工做過程爲:若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每個層次的加載器都是如此,所以全部的類加載請求都會傳給頂層的啓動類加載器,只有當父加載器反饋本身沒法完成該加載請求(該加載器的搜索範圍中沒有找到對應的類)時,子加載器纔會嘗試本身去加載。
Dalvik虛擬機如同其餘Java虛擬機同樣,在運行程序時首先須要將對應的類加載到內存中。而在Java標準的虛擬機中,類加載能夠從class文件中讀取,也能夠是其餘形式的二進制流,所以,咱們經常利用這一點,在程序運行時手動加載Class,從而達到代碼動態加載執行的目的。
然而Dalvik虛擬機畢竟不算是標準的Java虛擬機,所以在類加載機制和是哪一個,它們有相同的地方,也有不一樣之處,咱們必須區別對待。
在Android中提供了兩個ClassLoader的子類:PathClassLoader是經過構造函數new DexFile(path)來產生DexFile對象的;而DexClassLoader則是經過其靜態方法loadDex(path,outpath,0)獲得DexFile對象。DexClassLoader和PathClassLoader。這二者的區別在於DexClassLoader須要提供一個可寫的outpath路徑,用來釋放.apk包或者.jar包中的dex文件。換個說法來講,就是PathClassLoader不能主動從zip包中釋放出dex,所以只支持直接操做dex格式文件,或者已經安裝的apk(由於已經安裝的apk在cache中存在緩存的dex文件)。而DexClassLoader能夠支持.apk、.jar和.dex文件,而且會在指定的outpath路徑釋放出dex文件。
參考:https://www.jianshu.com/p/a620e368389a