對(duì)Android類加載器最全面的分析

心得體會(huì):學(xué)習(xí)不僅僅只是看教程,最好能夠想出代碼實(shí)例去驗(yàn)證自己對(duì)某個(gè)方面的理解和判斷,這樣不僅能加深理解,還能夠在未來的應(yīng)用開發(fā)中使用到。

前言

本篇文章針對(duì)Java 8 之前的類加載結(jié)構(gòu),Java 9做了不少改變,有興趣可以查看相關(guān)資料。

虛擬機(jī)類加載機(jī)制

類從被加載到虛擬機(jī)內(nèi)存開始,到卸載出內(nèi)存為止,它的整個(gè)生命周期包括:

image

1. 加載

  • 通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制流。

  • 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)

  • 在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種訪問入口。

注意,這里第1條中的二進(jìn)制字節(jié)流并不只是單純地從Class文件中獲取,比如它還可以從Jar包中獲取、從網(wǎng)絡(luò)中獲取(最典型的應(yīng)用便是Applet)、由其他文件生成(JSP應(yīng)用)等。

2. 鏈接:

  • 驗(yàn)證:確保被加載類的正確性;

  • 準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認(rèn)值;

  • 解析:把類中的符號(hào)引用轉(zhuǎn)換為直接引用;(解析階段則不一定,它在某些情況下可以在初始化階段之后開始)

3. 初始化

jvm有嚴(yán)格的規(guī)定(五種情況):

  1. 遇到new,getstatic,putstatic,invokestatic這4條字節(jié)碼指令時(shí),假如類還沒進(jìn)行初始化,
    則馬上對(duì)其進(jìn)行初始化工作。
    其實(shí)就是3種情況:用new實(shí)例化一個(gè)類時(shí)、讀取或者設(shè)置類的靜態(tài)字段時(shí)(不包括被final修飾的靜態(tài)字段,
    因?yàn)樗麄円呀?jīng)被塞進(jìn)常量池了)、以及執(zhí)行靜態(tài)方法的時(shí)候。

  2. 使用java.lang.reflect.*的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,
    如果類還沒有進(jìn)行過初始化,馬上對(duì)其進(jìn)行。

  3. 初始化一個(gè)類的時(shí)候,如果他的父親還沒有被初始化,則先去初始化其父親。

  4. 當(dāng)jvm啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含static void main(String[] args)的那個(gè)類),
    則jvm會(huì)先去初始化這個(gè)類。

  5. 用Class.forName(String className);來加載類的時(shí)候,也會(huì)執(zhí)行初始化動(dòng)作。
    注意:ClassLoader的loadClass(String className);方法只會(huì)加載并編譯某類,并不會(huì)對(duì)其執(zhí)行初始化。

對(duì)于這5種會(huì)觸發(fā)類初始化的場景,虛擬機(jī)使用了一個(gè)很強(qiáng)烈的限定語,“有且只有”,這5種場景的行為稱為對(duì)一個(gè)類進(jìn)行主動(dòng)引用。除此之外,所有引用類的方法都不會(huì)觸發(fā)初始化。

  • 如:通過子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化;
  • 通過數(shù)組定義引用類,不會(huì)觸發(fā)此類的初始化;
  • 常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上沒有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化。

初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程。<clinit>()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語句塊(static{}塊)中的語句合并產(chǎn)生的 。

這里借網(wǎng)絡(luò)上一張圖片總結(jié)一下,有興趣可參考:Java面試相關(guān)(一)-- Java類加載全過程(覺得圖畫的很細(xì)致,如若侵權(quán),請(qǐng)聯(lián)系作者)

image

雙親委派模型

image

看結(jié)構(gòu)圖(組合關(guān)系,非繼承關(guān)系)

image

從圖中我們發(fā)現(xiàn)除啟動(dòng)類加載器外,每個(gè)加載器都有父的類加載器。
雙親委派機(jī)制:如果一個(gè)類加載器在接到加載類的請(qǐng)求時(shí),它首先不會(huì)自己嘗試去加載這個(gè)類,而是把這個(gè)請(qǐng)求任務(wù)委托給父類加載器去完成,依次遞歸,如果父類加載器可以完成類加載任務(wù),就成功返回;只有父類加載器無法完成此加載任務(wù)時(shí),才自己去加載。

優(yōu)勢:Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系,避免了重復(fù)加載類,保障了Java類型體系的安全。

當(dāng)然你也可以破壞雙親委派模型,尤其在Android插件化中。

protected Class<?> loadClass(String name, boolean resolve)
     throws ClassNotFoundException
 {
         // First, check if the class has already been loaded
         Class c = findLoadedClass(name);//判斷類是否已經(jīng)加載過
         if (c == null) {
             long t0 = System.nanoTime();
             try {
                 if (parent != null) {
                     c = parent.loadClass(name, false);//父類加載器優(yōu)先加載
                 } 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.
                 long t1 = System.nanoTime();
                 c = findClass(name);//調(diào)用當(dāng)前類加載器的findClass方法進(jìn)行加載
                 // this is the defining class loader; record the stats
             }
         }
         return c;
 }

源碼分析:

簡單來說,java的雙親委派機(jī)制分為三個(gè)過程,在ClassLoader的loadClass方法中會(huì)先判斷該類是否已經(jīng)加載,若加載了直接返回,若沒加載過則先調(diào)用父類加載器的loadClass方法進(jìn)行類加載,若父類加載器沒有找到,則會(huì)調(diào)用當(dāng)前正在查找的類加載器的findClass方法進(jìn)行加載。

如果想保證自定義的類加載器符合雙親委派機(jī)制,則覆寫findClass方法;如果想打破雙親委派機(jī)制,則覆寫loadClass方法。

破壞雙親委派機(jī)制:

public class MyClassLoader extends DexClassLoader{
    public MyClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super(dexPath, optimizedDirectory, librarySearchPath, parent);
    }
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        if(xxx){//條件判斷是否自己加載
            return this.loadClass(name);
        }else{//雙親委派機(jī)制加載
            return super.loadClass(name, resolve);
        }
    }
}

父類,子類加載順序

父類代碼:

public class A{
    static{
        System.out.println("父類-靜態(tài)代碼塊");
    }
    {
        System.out.println("父類-非靜態(tài)代碼塊");
    }
    public A(){
        System.out.println("父類-構(gòu)造方法");
    }
}

子類代碼:

public class B extends A{
    static{
        System.out.println("子類-靜態(tài)代碼塊");
    }
    {
        System.out.println("子類-非靜態(tài)代碼塊");
    }
    public B(){
        System.out.println("子類-構(gòu)造方法");
    }
}

測試一下:

public class Test{
    public static void main(String[] args) {
        B b = new B();
    }
}

看看效果:

父類-靜態(tài)代碼塊
子類-靜態(tài)代碼塊
父類-非靜態(tài)代碼塊
父類-構(gòu)造方法
子類-非靜態(tài)代碼塊
子類-構(gòu)造方法

看到這,就知道初始化子類會(huì)先初始化父類。順序?yàn)?父類靜態(tài)——》子類靜態(tài)——》父類非靜態(tài)代碼塊——》父類構(gòu)造方法——》子類非靜態(tài)代碼塊——》子類構(gòu)造方法。

以上根本原因:初始化一個(gè)類的時(shí)候,如果他的父親還沒有被初始化,則先去初始化其父親。

Android中類加載器介紹

android中的類加載器中主要包括三類BootClassLoader,PathClassLoader和DexClassLoader。

BootClassLoader主要用于加載系統(tǒng)的類,包括java和android系統(tǒng)的類庫。

PathClassLoader主要用于加載應(yīng)用內(nèi)中的類。路徑是固定的,只能加載
/data/app中的apk,無法指定解壓釋放dex的路徑。所以PathClassLoader是無法實(shí)現(xiàn)動(dòng)態(tài)加載的。

DexClassLoader可以用于加載任意路徑的zip,jar或者apk文件??梢詫?shí)現(xiàn)動(dòng)態(tài)加載。下面來具體看看應(yīng)用程序中的類加載器。

Android的BootClassLoader和Java的BootStrapClassLoader區(qū)別:
  • Android虛擬機(jī)中BootClassLoader是ClassLoader內(nèi)部類,由java代碼實(shí)現(xiàn)而不是c++實(shí)現(xiàn),是Android平臺(tái)上所有ClassLoader的最終parent,這個(gè)內(nèi)部類是包內(nèi)可見,所以我們沒法使用。

  • Java虛擬機(jī)中BootStrapClassLoader是由原生代碼(C++)編寫的,負(fù)責(zé)加載java核心類庫(例如rt.jar等) .

補(bǔ)充知識(shí)點(diǎn):

Log.i("ljj", "Context的類加載器:"+ Context.class.getClassLoader());
Log.i("ljj", "TextView的類加載器: "+ TextView.class.getClassLoader());
//打印結(jié)果
02-14 12:37:49.161 22341-22341/com.ljj.host I/ljj: Context的類加載器:java.lang.BootClassLoader@a645091
02-14 12:37:49.162 22341-22341/com.ljj.host I/ljj: TextView的類加載器: java.lang.BootClassLoader@a645091

可見系統(tǒng)的類都是由BootClassLoader加載完成。

Log.i("ljj", "classLoader:"+getClassLoader());
02-14 13:19:23.730 20518-20518/com.ljj.host I/ljj: classLoader:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.ljj.host-2/base.apk"],nativeLibraryDirectories=[/data/app/com.ljj.host-2/lib/arm64, /vendor/lib64, /system/lib64]]]

DexClassLoader:

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}

DexClassLoader的源碼很簡單,只包含一個(gè)構(gòu)造函數(shù),看來所有的工作都是在BaseDexClassLoader中完成的。這里再看BaseDexClassLoader前,先說一下DexClassLoader構(gòu)造函數(shù)的四個(gè)參數(shù)。

  • dexPath:是加載apk/dex/jar的路徑
  • optimizedDirectory:是dex的輸出路徑(因?yàn)榧虞dapk/jar的時(shí)候會(huì)解壓除dex文件,這個(gè)路徑就是保存dex文件的)
  • librarySearchPath:是加載的時(shí)候需要用到的lib庫,這個(gè)一般不用,可以傳入Null
  • parent:給DexClassLoader指定父加載器
    下面繼續(xù)分析BaseClassLoader。
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, optimizedDirectory);
    }
   @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }

可以看出,DexClassLoader會(huì)通過傳入的路徑構(gòu)造出一個(gè)DexPathList對(duì)象,作為pathList。從findClass方法可以看出來加載的類都是從pathList中查找。至于DexPathList對(duì)象的源碼就不往下具體分析了,簡單的理解就是將每個(gè)dex都構(gòu)建成Element元素,放入到dexElements數(shù)組中,多說一句,這個(gè)dexElements數(shù)組的用處很大,MultiDex方案以及由此衍生出的QQ空間熱更新方案都是通過改變dexElements數(shù)組的元素位置來實(shí)現(xiàn)的。感興趣可以參考:Android類加載器分析

Android類加載器和Java的類加載器工作機(jī)制是類似的,使用雙親委托機(jī)制。

參考:

聲明:此為原創(chuàng),轉(zhuǎn)載請(qǐng)聯(lián)系作者


作者:微信公眾號(hào)添加公眾號(hào)-遛狗的程序員 ,或者可以掃描以下二維碼關(guān)注相關(guān)技術(shù)文章。

qrcode_for_gh_1ba0785324d6_430.jpg

當(dāng)然喜愛技術(shù),樂于分享的你也可以可以添加作者微信號(hào):

WXCD.jpeg
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容