Android中ClassLoader雙親委託機制

前言

一個Java程序,會通過javac編譯成class文件,然後通過虛擬機加載(ClassLoader)到方法區,執行引擎會執行這些字節碼,並翻譯成操作系統底層相關函數。這是JVM運行java代碼的整體流程。

由於Java中的ClassLoader類加載機制和Android中是不同的,本文將介紹Android虛擬機的類加載機制

Dex

瞭解JVM的老鐵都知道,JVM運行的是Class字節碼。
由於Class字節碼文件IO操作比較多、查找類效率低、基於棧的加載模式加載速度慢以及內存佔用較大的缺點,並不適用於移動端。所以DVM(Dalvik VM)設計了一種壓縮文件的格式Dex(Dalvik Executable Format)
Dex文件是由多個.class文件處理壓縮後的產物,Dex最終可以在Android運行時環境執行。

ART與Dalvik

1.DVM(Dalvik VM)是實現了JVM虛擬機規範的一個虛擬機,默認使用CMS垃圾回收器,與JVM不同的是DVM運行Dex文件;Android應用程序運行在虛擬機,一個進程對應一個單獨的虛擬機實例,所以一個Android應用至少有對應一個單獨的虛擬機實例
2.ART(Android Runtime)是在Anndroid 4.4版本引入的一個開發者選項,也是Android5.0及以上版本默認的Android運行時。ART虛擬機執行的是本地機器碼(預編譯得到機器碼)。Android的運行時從Dalvik換成ART並不需要開發者將應用編譯成機器碼,APK任然是一個包含dex字節碼的文件(ART和Dalvik都是運行Dex字節碼的兼容運行時,因此針對Dalvik開發的應用也能運行在ART虛擬機中。)
3.ART與Dalvik的差別是二者執行的指令集不同,ART的指令集是基於寄存器的,Dalvik的指令集是基於堆棧的。

dexopt與dexaot

1.dexopt:在Dalvik加載一個dex文件的時候,會對dex文件進行驗證和優化,驗證和優化後變成odex(Optimized dex [/'ɒptɪmaɪzd/])文件,odex與dex很像,只是使用了一些優化操作碼;
2.dexaot:ART預先編譯機制,在應用安裝時對dex文件執行AOT(Ahead-Of-Time)提前編譯操作,編譯爲OAT(OAT文件本質上是一個ELF文件,它將OAT文件格式內嵌在ELF文件裏)可執行文件(機器碼)

Android N(7.0)混合編譯

在這裏插入圖片描述
有沒有發現Android應用從7.0版本開始在手機配置差別不大的情況下安裝應用變得比之前的Android版本下快了
這就得益於Android N的混合編譯
ART使用AOT(Ahead-Of-Time)編譯,在應用安裝在應用安裝時對dex文件提前編譯。使得安裝變得緩慢,從Android N開始混合使用AOT編譯+解釋+JIT
1.初次安裝應用時不進行AOT編譯,運行過程中解釋執行,對經常執行的方法進行JIT,經過JIT編譯的方法會記錄到Profile配置文件中
2.當處於設備閒置或充電或有四個小時間隔時,編譯守護進程會運行,根據Profile文件對常用代碼(“熱代碼”)進行AOT(All Of the Time compilation:全時段編譯)編譯,生成base.art文件(稱爲 app_image 類對象映像)。這個art文件會在apk啓動時會加入到PathClassloader的ClassTable中,系統在查找類的時候會先查找base.art中的。
Android N混合編譯主要解決的問題:

1.應用安裝時間過長;在N之前,應用在安裝時需要對所有ClassN.dex做AOT機器碼編譯,類似微信這種比較大型的APP可能會耗時數分鐘。但是往往我們只會使用一個應用20%的功能,剩下的80%我們付出了時間成本,卻沒帶來太大的收益。
2.降低佔ROM空間;同樣全量編譯AOT機器碼,12M的dex編譯結果往往可以達到50M之多。只編譯用戶用到或常用的20%功能,這對於存儲空間不足的設備尤其重要。
3.提升系統與應用性能;減少了全量編譯,降低了系統的耗電。在boot.art的基礎上,每個應用增加了base.art, 通過預加載與緩存提升應用性能。
4.快速的系統升級;以往廠商ota時,需要對安裝的所有應用做全量的AOT編譯,這耗時非常久。事實上,同樣只有20%的應用是我們經常使用的,給不常用的應用,不常用的功能付出的這些成本是不值得的。

ClassLoader介紹

Android9.0下ClassLoader的UML類圖
在這裏插入圖片描述
一個Java程序,會通過javac編譯成一個或多個class文件,運行的時候,需要將class文件加載到虛擬機中使用,負責加載這些文件的就是Java的類加載機制。
ClassLoader的作用就是加載class文件到方法區,提供給程序運行的時候使用。
ClassLoader是一個抽象類。具體的實現類有三個

1.BootClassLoader
BootClassLoader用於加載Android Framework層的class文件

2.PathClassLoader
PathClassLoader是Android應用程序的類加載器(加載已安裝應用的dex)可以加載指定的dex和在java、zip、apk中的dex
在Dalvik虛擬機上PathClassLoader只能加載已安裝的apk的dex,Android5.0開始使用的ART虛擬機,PathClassLoader也可以加載指定的dex和在java、zip、apk中的dex
3.DexClassLoader
DexClassLoader用於加載指定的dex和在java、zip、apk中的dex

雙親委託機制

雙親委託機制原理

某個類加載器在加載類的時候,首先委託給父加載器(ClassLoader parent)加載,其父加載器再委託給它的父加載器(如果有的話),以此類推。
如果父加載器可以完成加載類的任務的話,就成功返回
如果父加載器不能完成加載類的任務或者沒有父加載器的話,自己去加載類。

使用雙親委託機制目的

1.安全。防止核心API庫被篡改。

即使用戶自定義的類與java核心api中的類名相同,因爲雙親委託機制首先會使用父類加載器加載,由於PathClassLoader是Android應用程序的類加載器,應用開發者自己寫的代碼由PathClassLoader加載,PathClassLoader的父加載器是BootClassLoader,核心API類的Class已經被BootClassLoader加載過了,所以用戶自定義的類不會加載。比如開發者自定義了一個String類,如果沒有雙親委託機制,就會篡改String類
在這裏插入圖片描述

爲什麼PathClassLoader的父加載器是BootClassLoader?
我的這篇文章有介紹
這裏需要注意一下,父加載器不是父類加載器,兩者不是繼承和被繼承的關係,而是parent是創建ClassLoader的一個參數

2.避免重複加載。當一個類被父類加載器加載過的時候,就沒必要再用子類加載器加載一遍了。

ClassLoader.loadClass()關鍵代碼

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded 檢查class是否已經被加載
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    //父類加載器不爲null就用父類加載器加載
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    //父類加載器爲null就用BootClassLoader加載
                        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;
    }

參考

Android N混合編譯與對熱補丁影響解析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章