傳統Jvm
java虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別,這就是虛擬機器的類載入機制。
類的生命週期
類從被載入到虛擬機器記憶體中開始,到解除安裝出記憶體為止,它的整個生命週期包括:載入(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和解除安裝(Unloading)7個階段。其中驗證、準備、解析3個部分統稱為連線(Linking),這7個階段的發生順序如圖所示
類載入器
對於任意一個類,都需要由載入它的類載入器和這個類本身一同確立其在Java虛擬機器中的唯一性,每一個類載入器,都擁有一個獨立的類名稱空間。這句話可以表達得更通俗一些:**比較兩個類是否“相等”,只有在這兩個類是由同一個類載入器載入的前提下才有意義,否則,即使這兩個類來源於同一個Class檔案,被同一個虛擬機器載入,只要載入它們的類載入器不同,那這兩個類就必定不相等。 **
雙親委派模型
絕大部分Java程式都會使用到以下3種系統提供的類載入器:
- 啟動類載入器(Bootstrap ClassLoader)
這個類將器負責將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath引數所指定的路徑中的,並且是虛擬機器識別的(僅按照檔名識別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會被載入)類庫載入到虛擬機器記憶體中。啟動類載入器無法被Java程式直接引用,使用者在編寫自定義類載入器時,如果需要把載入請求委派給引導類載入器,那直接使用null代替即可。
- 擴充套件類載入器(Extension ClassLoader)
這個載入器由sun.misc.Launcher $ExtClassLoader實現,它負責載入<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變數所指定的路徑中的所有類庫,開發者可以直接使用擴充套件類載入器。
- 應用程式類載入器(Application ClassLoader)
這個類載入器由sun.misc.Launcher $App-ClassLoader實現。由於這個類載入器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱它為系統類載入器。它負責載入使用者類路徑(ClassPath)上所指定的類庫,開發者可以直接使用這個類載入器,如果應用程式中沒有自定義過自己的類載入器,一般情況下這個就是程式中預設的類載入器。
java應用程式一般都是由這3種類載入器互相配合進行載入的,如果有必要,還可以加入自己定義的類載入器。這些類載入器之間的關係一般如圖所示。
上圖中展示的類載入器之間的這種層次關係,稱為類載入器的雙親委派模型(Parents Delegation Model)。雙親委派模型要求除了頂層的啟動類載入器外,其餘的類載入器都應當有自己的父類載入器。這裡類載入器之間的父子關係一般不會以繼承(Inheritance)的關係來實現,而是都使用組合(Composition)關係來複用父載入器的程式碼。
雙親委派模型的工作過程是:如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成,每一個層次的類載入器都是如此,因此所有的載入請求最終都應該傳送到頂層的啟動類載入器中,只有當父載入器反饋自己無法完成這個載入請求(它的搜尋範圍中沒有找到所需的類)時,子載入器才會嘗試自己去載入。
Android的ClassLoader機制
本質上,Android和傳統的JVM是一樣的,也需要通過ClassLoader 將目標類載入到記憶體,類載入器之間也符合雙親委派模型,類也有對應的生命週期。但基於移動裝置的特點,如記憶體以及電量等諸多方面跟一般的 PC 裝置都有本質的區別,Google開發了更符合移動裝置的用於執行 Java 程式碼的虛擬機器,也就是Dalvik和 ART,Android從5.0開始就採用AR虛擬機器替代Dalvik。傳統Jvm主要是通過讀取class位元組碼來載入, 而ART則是從dex位元組碼來讀取. 這是一種更為優化的方案, 可以將多個.class檔案合併成一個classes.dex檔案。
Classloader關係圖
BaseDexClassLoader
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
...
...
Class c = pathList.findClass(name, suppressedExceptions);
...
...
return c;
}
@Override
protected URL findResource(String name) {
return pathList.findResource(name);
}
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
}
複製程式碼
可以看到在建構函式裡初始化了DexPathList物件,而在BaseDexClassLoader中的操作findClass
、findResource
執行的都是這個DexPathList物件的操作,關於DexPathList,在此暫不展開。
從DexPathList的構造過程可以看到,無論optimizedDirectory是何值,傳遞的都是空,所以optimizedDirectory引數是無效的(從Android8.0開始)
PathClassLoader
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
複製程式碼
PathClassLoader比較簡單, 繼承於BaseDexClassLoader. 預設 optimizedDirectory=null.
DexClassLoader
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
複製程式碼
DexClassLoader也比較簡單, 只是簡單封裝,和PathClassLoader唯一區別就是多了optimizedDirectory引數,但從上面BaseDexClassLoader
分析可以知道,從8.0開始optimizedDirectory已經棄用。從理論上來說,PathClassLoader應該可以完全替代DexClassLoader。但網上有這樣的結論:
DexClassLoader:能夠載入未安裝的apk
PathClassLoader:只能載入系統中已經安裝過的apk
那麼在8.0以上這個結論還成立嗎,事實上PathClassLoader也可以載入未安裝的apk,驗證過程比較簡單,不在此累贅,有興趣可以自己試試。