對Android類加載器最全面的分析

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

前言

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

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

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

image

1. 加載

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

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

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

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

2. 鏈接:

  • 驗證:確保被加載類的正確性密强;

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

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

3. 初始化

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

  1. 遇到new奕扣,getstatic薪鹦,putstatic,invokestatic這4條字節(jié)碼指令時,假如類還沒進(jìn)行初始化池磁,
    則馬上對其進(jìn)行初始化工作奔害。
    其實就是3種情況:用new實例化一個類時、讀取或者設(shè)置類的靜態(tài)字段時(不包括被final修飾的靜態(tài)字段地熄,
    因為他們已經(jīng)被塞進(jìn)常量池了)华临、以及執(zhí)行靜態(tài)方法的時候。

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

  3. 初始化一個類的時候跛梗,如果他的父親還沒有被初始化寻馏,則先去初始化其父親。

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

  5. 用Class.forName(String className);來加載類的時候漾岳,也會執(zhí)行初始化動作轰绵。
    注意:ClassLoader的loadClass(String className);方法只會加載并編譯某類,并不會對其執(zhí)行初始化尼荆。

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

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

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

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

image

雙親委派模型

image

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

image

從圖中我們發(fā)現(xiàn)除啟動類加載器外盒齿,每個加載器都有父的類加載器。
雙親委派機(jī)制:如果一個類加載器在接到加載類的請求時,它首先不會自己嘗試去加載這個類县昂,而是把這個請求任務(wù)委托給父類加載器去完成,依次遞歸陷舅,如果父類加載器可以完成類加載任務(wù)倒彰,就成功返回;只有父類加載器無法完成此加載任務(wù)時莱睁,才自己去加載待讳。

優(yōu)勢:Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(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ī)制分為三個過程,在ClassLoader的loadClass方法中會先判斷該類是否已經(jīng)加載部凑,若加載了直接返回露乏,若沒加載過則先調(diào)用父類加載器的loadClass方法進(jìn)行類加載,若父類加載器沒有找到涂邀,則會調(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)造方法

看到這观蜗,就知道初始化子類會先初始化父類。順序為 父類靜態(tài)——》子類靜態(tài)——》父類非靜態(tài)代碼塊——》父類構(gòu)造方法——》子類非靜態(tài)代碼塊——》子類構(gòu)造方法赡勘。

以上根本原因:初始化一個類的時候嫂便,如果他的父親還沒有被初始化,則先去初始化其父親闸与。

Android中類加載器介紹

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

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

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

DexClassLoader可以用于加載任意路徑的zip,jar或者apk文件忽洛∧寤荩可以實現(xiàn)動態(tài)加載。下面來具體看看應(yīng)用程序中的類加載器欲虚。

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

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

補充知識點:

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òu)造函數(shù)燥筷,看來所有的工作都是在BaseDexClassLoader中完成的。這里再看BaseDexClassLoader前锈锤,先說一下DexClassLoader構(gòu)造函數(shù)的四個參數(shù)驯鳖。

  • dexPath:是加載apk/dex/jar的路徑
  • optimizedDirectory:是dex的輸出路徑(因為加載apk/jar的時候會解壓除dex文件,這個路徑就是保存dex文件的)
  • librarySearchPath:是加載的時候需要用到的lib庫牙咏,這個一般不用臼隔,可以傳入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會通過傳入的路徑構(gòu)造出一個DexPathList對象摔握,作為pathList。從findClass方法可以看出來加載的類都是從pathList中查找丁寄。至于DexPathList對象的源碼就不往下具體分析了氨淌,簡單的理解就是將每個dex都構(gòu)建成Element元素,放入到dexElements數(shù)組中伊磺,多說一句盛正,這個dexElements數(shù)組的用處很大,MultiDex方案以及由此衍生出的QQ空間熱更新方案都是通過改變dexElements數(shù)組的元素位置來實現(xiàn)的屑埋。感興趣可以參考:Android類加載器分析

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

參考:

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


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

qrcode_for_gh_1ba0785324d6_430.jpg

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

WXCD.jpeg
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逻恐,一起剝皮案震驚了整個濱河市像吻,隨后出現(xiàn)的幾起案子峻黍,更是在濱河造成了極大的恐慌,老刑警劉巖拨匆,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姆涩,死亡現(xiàn)場離奇詭異,居然都是意外死亡惭每,警方通過查閱死者的電腦和手機(jī)阵面,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洪鸭,“玉大人,你說我怎么就攤上這事仑扑±谰簦” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵镇饮,是天一觀的道長蜓竹。 經(jīng)常有香客問我,道長储藐,這世上最難降的妖魔是什么俱济? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮钙勃,結(jié)果婚禮上蛛碌,老公的妹妹穿的比我還像新娘。我一直安慰自己辖源,他們只是感情好蔚携,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著克饶,像睡著了一般酝蜒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上矾湃,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天亡脑,我揣著相機(jī)與錄音,去河邊找鬼邀跃。 笑死霉咨,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的坞嘀。 我是一名探鬼主播躯护,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼丽涩!你這毒婦竟也來了棺滞?” 一聲冷哼從身側(cè)響起裁蚁,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎继准,沒想到半個月后枉证,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡移必,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年室谚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片崔泵。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡秒赤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出憎瘸,到底是詐尸還是另有隱情入篮,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布幌甘,位于F島的核電站潮售,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏锅风。R本人自食惡果不足惜酥诽,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望皱埠。 院中可真熱鬧肮帐,春花似錦、人聲如沸边器。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饰抒。三九已至肮砾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間袋坑,已是汗流浹背仗处。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留枣宫,地道東北人婆誓。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像也颤,于是被迫代替她去往敵國和親洋幻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容