ClassLoader種類:
-BootClassLoader
-PathClassLoader
-DexClassLoader
-BaseDexClassLoader
BootClassLoader: 主要用於加載系統的類,包括java和android系統的類庫。 (例如TextView、Context等)
PathClassLoader: 加載安裝應用裏面的字節碼文件。
DexClassLoader:指定目錄中的字節碼class文件。
BaseDexClassLoader:是PathClassLoader和DexClassLoader的父類
總結:
- 通過上面的介紹,得知:一個應用至少會有兩個classLoader類,BootClassLoader用於加載系統類
- 另一個是PathClassLoader,加載我們APP裏面的類,路徑是固定的,只能加載/data/app中的apk,所以無法實現動態加載
- 當然我們也可以添加DexClassLoader,實現熱修復和動態加載
- 無論哪個類加載器加載後,都會緩存到內存中去,下次直接在內存中獲取
類加載流程
類加載的特點
-雙親代理模型特點
-類加載共享功能
-類加載的隔離功能
雙親委託模式
下面是java JVM的幾個類加載器 / 右邊是android 類加載器
名稱 | 加載 | 加載路徑 | 父加載器 | 實現 |
---|---|---|---|---|
BootStrap / BootClassLoader | 虛擬機的核心類庫 | sun.boot.class.path | 無 | 系統 |
Extension | 擴展類庫 | java.ext.dirs、jre/lib/ext | BootStrap | Java |
System / PathClassLoader | 應用類庫 | classpath、java.class.path | Extension | Java |
注:父子加載器並非繼承關係,也就是說子加載器不一定是繼承了父加載器
然後我們用一張圖來梳理父類關係:
ClassLoader加載流程:
總結:
- 自定義ClassLoader向自己的上層(Application ClassLoader)請求
- Application ClassLoader繼續向上層(Extension ClassLoader)請求
- Extension ClassLoader繼續向上層(Bootstrap ClassLoader)請求
- Bootstrap ClassLoader是最上層類加載器,所以它嘗試在自己的路徑中查找要加載類,如果查找到了就加載類,否則向下層(Extension ClassLoader)反饋自己無法加載類。
- Extension ClassLoader從自己的路徑中尋找要加載類,找到則加載,找不到則繼續向下返回。
- Application ClassLoader從自己的路徑中尋找要加載類,找到則加載,找不到則繼續向下返回。
- 自定義ClassLoader從自己的路徑中尋找要加載類,找到則加載。由於類加載請求是自定義ClassLoader發起的,所以當它自己也找不到要加載的類時會終止加載並拋出
ClassNotFoundException。
classLoader是通過雙親代理加載特點:
首先會在會緩存中查找(父類有沒有加載過,如果有直接返回。 沒有的話,會委託先讓父加載器去加載,如果父類不能加載,自己在加載。
優點:
- 安全,將核心類庫與用戶類庫隔離,用戶不能通過加載器替換核心類庫,如String類。(假設有一個開發者自己編寫了一個名爲java.lang.Object的類,想借此欺騙JVM。現在他要使用自定義ClassLoader來加載自己編寫的java.lang.Object類。然而幸運的是,雙親委託模型不會讓他成功。因爲JVM會優先在BootstrapClassLoader的路徑下找到java.lang.Object類,並載入它。)
- 效率快\避免類庫重複加載
缺點:
委託永遠是子加載器去請求父加載器,是單向的,即上層的類加載器無法訪問下層的類加載器所加載的類:
如何解決弊端——使用「SPI」
「SPI」 全稱爲 (Service Provider Interface) ,是JDK內置的一種服務提供發現機制。
目前有不少框架用它來做服務的擴展發現, 簡單來說,它就是一種動態替換髮現的機制。
每一個線程都有自己的ContextClassLoader,默認以SystemClassLoader爲ContextClassLoader。通過Thread.currentThread().getContextClassLoader(),可以把一個ClassLoader置於一個線程的實例之中,使該ClassLoader成爲一個相對共享的實例,這樣即使是啓動類加載器中的代碼也可以通過這種方式訪問應用類加載器中的類了。
SPI的弊端
「SPI」通過循環加載實現類,顯而易見,它會把所有的類一同加載,無論有沒有用到,這造成了一定的資源浪費.
類加載器是否一定遵循雙親委託模式?
這個答案是否定的。
雙親委託模型只是JDK提供的ClassLoader類的實現方式。
在實際開發中,我們可以通過自定義ClassLoader,並重寫父類的loadClass方法,來打破這一機制。
類加載的共享功能: 我們fromwork層級的類(系統類)一旦被我們頂層的BootClassLoader文件加載過,那麼久緩存到內存裏面,以後任何地方用到都不會重新加載
類加載的隔離功能: 不同的classLoader加載的類就是不同的類, 哪怕是類名和包名都一樣也不行
知識補充:
Log.d(TAG, TextView.class.getClassLoader()+"");
Log.d(TAG, new TextView(this).getClass().getClassLoader() + "");
Log.d(TAG, new Test().getClass().getClassLoader() + "");
Log.d(TAG, Test.class.getClassLoader()+"");
打印結果:
BootClassLoader
BootClassLoader
dalvik.system.PathClassLoader
dalvik.system.PathClassLoader