文章目錄
- 1. ClassLoader 是做什麼的?
- 2. JVM 類加載流程
- 3. 延遲加載
- 4. ClassLoader 傳遞性
- 5. 雙親委派
- 6. Class.forName
- 7. 自定義加載器
- Class.forName` vs `ClassLoader.loadClass
- 參考連結
1. ClassLoader 是做什麼的?
用來加載 Class 的。負責将 Class 的位元組碼形式轉換成 記憶體中的 Class 對象,位元組碼可以來自于磁盤檔案 *.class,也可以是 jar 包裡的 *.class,也可以是
.dex
中的 class,位元組碼的本質就是一個位元組數組
[]byte
,它有特定的複雜的内部格式。
每個
Class
對象的内部都有一個
classLoader
字段來辨別自己是由哪個
ClassLoader
加載的。
ClassLoader
就像一個容器,裡面裝了很多已經加載的
Class
對象。
class Class<T> {
...
private final ClassLoader classLoader;
...
}
2. JVM 類加載流程
Java語言系統自帶有三個類加載器:
-
負責加載 Bootstrap ClassLoader
運作時核心類,這些類位于 JVM
檔案中,我們常用内置庫 $JAVA_HOME/lib/rt.jar
都在裡面,比如 java.xxx.*
、java.io.java.util.
、、java.nio.
等等。這個 java.lang.
比較特殊,它是由 ClassLoader
代碼實作的,我們将它稱之為「根加載器」。C
-
負責加載 Extention ClassLoader
擴充類,比如 JVM
系列、内置的 swing
引擎、js
解析器 等等,這些庫名通常以 xml
開頭,它們的 javax
包位于 jar
中,有很多 $JAVA_HOME/lib/ext/*.jar
包。jar
-
才是直接面向我們使用者的加載器,它會加載 Appclass Loader
環境變量裡定義的路徑中的 Classpath
包和目錄。我們自己編寫的代碼以及使用的第三方 jar 包通常都是由它來加載的。jar
AppClassLoader
可以由
ClassLoader
類提供的靜态方法
getSystemClassLoader()
得到,它就是我們所說的「系統類加載器」,我們使用者平時編寫的類代碼通常都是由它加載的。當我們的
main
方法執行的時候,這第一個使用者類的加載器就是
AppClassLoader
。
3. 延遲加載
JVM
運作并不是一次性加載所需要的全部類的,它是按需加載,也就是延遲加載。程式在運作的過程中會逐漸遇到很多不認識的新類,這時候就會調用
ClassLoader
來加載這些類。加載完成後就會将
Class
對象存在
ClassLoader
裡面,下次就不需要重新加載了。
比如你在調用某個類的靜态方法時,首先這個類肯定是需要被加載的,但是并不會觸及這個類的執行個體字段,那麼執行個體字段的類别
Class
就可以暫時不必去加載,但是它可能會加載靜态字段相關的類别,因為靜态方法會通路靜态字段。而執行個體字段的類别需要等到你執行個體化對象的時候才可能會加載。
4. ClassLoader 傳遞性
程式在運作過程中,遇到了一個未知的類,它會選擇哪個
ClassLoader
來加載它呢?虛拟機的政策是使用調用者
Class
對象的
ClassLoader
來加載目前未知的類。何為調用者
Class
對象?就是在遇到這個未知的類時,虛拟機肯定正在運作一個方法調用(靜态方法或者執行個體方法),這個方法挂在哪個類上面,那這個類就是調用者
Class
對象。前面我們提到每個
Class
對象裡面都有一個 classLoader 屬性記錄了目前的類是由誰來加載的。
因為
ClassLoader
的傳遞性,所有延遲加載的類都會由初始調用
main
方法的這個
ClassLoader
全全負責,它就是
AppClassLoader
。
5. 雙親委派
前面我們提到
AppClassLoader
隻負責加載
Classpath
下面的類庫,如果遇到沒有加載的系統類庫怎麼辦,
AppClassLoader
必須将系統類庫的加載工作交給
BootstrapClassLoader
和
ExtensionClassLoader
來做,這就是我們常說的「雙親委派」。
AppClassLoader
在加載一個未知的類名時,它并不是立即去搜尋
Classpath
,它會首先将這個類名稱交給
ExtensionClassLoader
來加載,如果
ExtensionClassLoader
可以加載,那麼
AppClassLoader
就不用麻煩了。否則它就會搜尋
Classpath
。
而
ExtensionClassLoader
在加載一個未知的類名時,它也并不是立即搜尋
ext
路徑,它會首先将類名稱交給
BootstrapClassLoader
來加載,如果
BootstrapClassLoader
可以加載,那麼
ExtensionClassLoader
也就不用麻煩了。否則它就會搜尋
ext
路徑下的
jar
包。
這三個
ClassLoader
之間形成了級聯的父子關系,每個
ClassLoader
都很懶,盡量把工作交給父親做,父親幹不了了自己才會幹。每個
ClassLoader
對象内部都會有一個 parent 屬性指向它的父加載器。
class ClassLoader {
...
private final ClassLoader parent;
...
}
值得注意的是圖中的
ExtensionClassLoader
的
parent
指針畫了虛線,這是因為它的
parent
的值是
null
,當
parent
字段是
null
時就表示它的父加載器是「根加載器」。如果某個
Class
對象的
classLoader
屬性值是
null
,那麼就表示這個類也是「根加載器」加載的。注意這裡的
parent
不是
super
不是父類,隻是
ClassLoader
内部的字段。
6. Class.forName
forName
方法使用調用者
Class
對象的
ClassLoader
來加載目标類。不過
forName
還提供了多參數版本,可以指定使用哪個
ClassLoader
來加載。
Class<?> forName(String name, boolean initialize, ClassLoader cl)
通過這種形式的
forName
方法可以突破内置加載器的限制,通過使用自定類加載器允許我們自由加載其它任意來源的類庫。根據
ClassLoader
的傳遞性,目标類庫傳遞引用到的其它類庫也将會使用自定義加載器加載。
Android hotfix 中的應用;
/**
* 通過反射擷取BaseDexClassLoader對象中的PathList對象
*
* @param baseDexClassLoader BaseDexClassLoader對象
* @return PathList對象
*/
public static Object getPathList(Object baseDexClassLoader)
throws NoSuchFieldException, IllegalAccessException, IllegalArgumentException, ClassNotFoundException {
return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
}
7. 自定義加載器
ClassLoader
裡面有三個重要的方法
loadClass()
、
findClass()
和
defineClass()
。
-
方法是加載目标類的入口,它首先會查找目前loadClass()
以及它的雙親裡面是否已經加載了目标類,如果沒有找到就會讓雙親嘗試加載,如果雙親都加載不了,就會調用ClassLoader
讓自定義加載器自己來加載目标類。findClass()
-
方法是需要子類來覆寫的,不同的加載器将使用不同的邏輯來擷取目标類的位元組碼。findClass()
- 拿到這個位元組碼之後再調用
方法将位元組碼轉換成defineClass()
對象。Class
class ClassLoader {
// 加載入口,定義了雙親委派規則
Class loadClass(String name) {
// 是否已經加載了
Class t = this.findFromLoaded(name);
if(t == null) {
// 交給雙親
t = this.parent.loadClass(name)
}
if(t == null) {
// 雙親都不行,隻能靠自己了
t = this.findClass(name);
}
return t;
}
// 交給子類自己去實作
Class findClass(String name) {
throw ClassNotFoundException();
}
// 組裝Class對象
Class defineClass(byte[] code, String name) {
return buildClassFromCode(code, name);
}
}
class CustomClassLoader extends ClassLoader {
Class findClass(String name) {
// 尋找位元組碼
byte[] code = findCodeFromSomewhere(name);
// 組裝Class對象
return this.defineClass(code, name);
}
}
Class.forNamevsClassLoader.loadClass
Class<?> x = Class.forName("[I");
System.out.println(x);
x = ClassLoader.getSystemClassLoader().loadClass("[I");
System.out.println(x);
---------------------
class [I
Exception in thread "main" java.lang.ClassNotFoundException: [I
...
參考連結
- 老大難的 Java ClassLoader 再不了解就老了
- 一看你就懂,超詳細java中的ClassLoader詳解
- 深入了解JVM之ClassLoader