第05講 深入理解 ClassLoader 的加載機制
拉勾教育:https://kaiwu.lagou.com/course/courseInfo.htm
這一講主要講了class文件的加載時機,及加載class的雙親委派機制。還舉例說明了如何自定義classLoader。最後還介紹了android中的類加載器,並舉例使用自定義的類加載器進行熱修復。下面來一一看一下這些內容。
一、class文件的加載時機
在 Java 程序啓動的時候,並不會一次性加載程序中所有的 .class 文件,而是在程序的運行過程中,動態地加載相應的類到內存中。
通常情況下,Java 程序中的 .class 文件會在以下 2 種情況下被 ClassLoader 主動加載到內存中:
- 調用類構造器
- 調用類中的靜態(static)變量或者靜態方法
二、雙親委派模型
2.1 類加載器種類
從開發者的角度,類加載器可以細分爲:
-
啓動類加載器(BootstrapClassLoader):負責將 Java_Home/lib下面的類庫加載到內存中(比如rt.jar)。由於引導類加載器涉及到虛擬機本地實現細節,開發者無法直接獲取到啓動類加載器的引用,所以不允許直接通過引用進行操作。
-
標準擴展類加載器(ExtClassLoader ,JDK 1.9 之後,改名爲 PlatformClassLoader):是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實現的。它負責將Java_Home /lib/ext或者由系統變量 java.ext.dir指定位置中的類庫加載到內存中。開發者可以直接使用標準擴展類加載器。
-
應用程序類加載器(APPClassLoader):是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實現的。它負責將系統類路徑(CLASSPATH)中指定的類庫加載到內存中。開發者可以直接使用系統類加載器。由於這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般稱爲系統(System)加載器。
除此之外,還有自定義的類加載器,它們之間的關係通過被稱爲類加載器的雙親委派模型來體現。
2.2 雙親委派模型
用一張圖來表示各個類加載器之間的委派關係
雙親委派模型即某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務,就成功返回;只有父類加載器無法完成此加載任務時,才自己去加載。
使用雙親委派模型的好處在於Java類隨着它的類加載器一起具備了一種帶有優先級的層次關係。例如類java.lang.Object,它存在在rt.jar中,無論哪一個類加載器要加載這個類,最終都是委派給處於模型最頂端的Bootstrap ClassLoader進行加載,因此Object類在程序的各種類加載器環境中都是同一個類。相反,如果沒有雙親委派模型而是由各個類加載器自行加載的話,如果用戶編寫了一個java.lang.Object的同名類並放在ClassPath中,那系統中將會出現多個不同的Object類,程序將混亂。而在雙親委派模型,這個自已編寫的Object類可以正常編譯,但是永遠無法被加載運行。
雙親委派模型在代碼中的實現:
protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{
//check the class has been loaded or not
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
- 注意:“雙親委派”機制只是 Java 推薦的機制,並不是強制的機制。我們可以繼承 java.lang.ClassLoader 類,實現自己的類加載器。
- 如果想保持雙親委派模型,就應該重寫
findClass(name)
方法; - 如果想破壞雙親委派模型,可以重寫
loadClass(name)
方法。
三、自定義classLoader
JVM 中預置的 3 種 ClassLoader 只能加載特定目錄下的 .class 文件,如果我們想加載其他特殊位置下的 jar 包或類時(比如,我要加載網絡或者磁盤上的一個 .class 文件),默認的 ClassLoader 就不能滿足我們的需求了,所以需要定義自己的 Classloader 來加載特定目錄下的 .class 文件。
自定義 ClassLoader 步驟
- 自定義一個類繼承抽象類 ClassLoader。
- 重寫 findClass 方法。
- 在 findClass 中,調用 defineClass 方法將字節碼轉換成 Class 對象,並返回。
課程舉的例子,看下它的代碼:
注意:上述動態加載 .class 文件的思路,經常被用作熱修復和插件化開發的框架中,包括 QQ 空間熱修復方案、微信 Tink 等原理都是由此而來。客戶端只要從服務端下載一個加密的 .class 文件,然後在本地通過事先定義好的加密方式進行解密,最後再使用自定義 ClassLoader 動態加載解密後的 .class 文件,並動態調用相應的方法。
四、android中的類加載器
本質上,Android 和傳統的 JVM 是一樣的,也需要通過 ClassLoader 將目標類加載到內存,類加載器之間也符合雙親委派模型。但是在 Android 中, ClassLoader 的加載細節有略微的差別。
在 Android 虛擬機裏是無法直接運行 .class 文件的,Android 會將所有的 .class 文件轉換成一個 .dex 文件,並且 Android 將加載 .dex 文件的實現封裝在 BaseDexClassLoader 中,而我們一般只使用它的兩個子類:PathClassLoader 和 DexClassLoader。
PathClassLoader
PathClassLoader 用來加載系統 apk 和被安裝到手機中的 apk 內的 dex 文件。當一個 App 被安裝到手機後,apk 裏面的 class.dex 中的 class 均是通過 PathClassLoader 來加載的
DexClassLoader
先來看官方對 DexClassLoader 的描述:
A class loader that loads classes from .jar and .apk filescontaining a classes.dex entry.
This can be used to execute code notinstalled as part of an application.
很明顯,對比 PathClassLoader 只能加載已經安裝應用的 dex 或 apk 文件,DexClassLoader 則沒有此限制,可以從 SD 卡上加載包含 class.dex 的 .jar 和 .apk 文件,這也是插件化和熱修復的基礎,在不需要安裝應用的情況下,完成需要使用的 dex 的加載。
由於水平有限,如果文中存在錯誤之處,請大家批評指正,歡迎大家一起來分享、探討!
博客:http://blog.csdn.net/MingHuang2017
GitHub:https://github.com/MingHuang1024
Email: [email protected]
g2017)
GitHub:https://github.com/MingHuang1024
Email: [email protected]
微信:724360018