android虚拟机加载机制,Android虚拟机与类加载机制
JVM與Dalvik
Android應用程序運行在Dalvik/ART虛擬機,并且每一個應用程序對應有一個單獨的Dalvik虛擬機實例。Dalvik虛擬機實則也算是一個Java虛擬機,只不過它執行的不是class文件,而是dex文件。Dalvik虛擬機與Java虛擬機共享有差不多的特性,差別在于兩者執行的指令集是不一樣的,前者的指令集是基本寄存器的,而后者的指令集是基于堆棧的。
那什么是基于棧的虛擬機,什么又是基于寄存器的虛擬機?
基于棧的虛擬機
對于基于棧的虛擬機來說,每一個運行時的線程,都有一個獨立的棧。棧中記錄了方法調用的歷史,每有一次方法調用,棧中便會多一個棧楨。最頂部的棧楨稱作當前棧楨,其代表著當前執行的方法?;跅5奶摂M機通過操作數棧進行所有操作。
字節碼指令
ICONST_1 : 將int類型常量1壓入操作數棧;
ISTORE 0 : 將棧頂int類型值存入局部變量0;
IADD : 執行int類型的加法 ;
基于棧的虛擬機在執行方法時,需要將數據讀取到操作數棧,然后再將數據寫入棧幀中的局部變量表等操作。
執行過程
(0)PC計數器指向0行,執行ICONST_1指令,將常量1壓入操作數棧中
(1)執行ISTORE_0指令,將操作數棧的棧頂元素存儲到局部變量表的第1個位置
(2)執行ICONST_2指令,加載數字2到操作數棧
(3)執行ISTORE_1將操作數棧中的數據2存儲到局部變量表的第2個位置
(4)執行ILOAD 0將局部變量表中的第1個位置的數字加載到操作數棧中
(5)執行ILOAD 1將局部變量表中的第2個位置的數字加載到操作數棧中
(6)執行IADD 將操作數棧的棧頂的兩個數相加,并將所得結果壓如棧頂
(7)將操作數棧頂的數據存儲到局部變量表的第3個位置
(8)執行RETUREN指令,方法結束。
基于寄存器的虛擬機
寄存器是CPU的組成部分。寄存器是有限存貯容量的高速存貯部件,它們可用來暫存指令、數據和位址
寄存器
基于寄存器的虛擬機中沒有操作數棧,但是有很多虛擬寄存器。其實和操作數棧相同,這些寄存器也存放在運行時棧中,本質上就是一個數組。與JVM相似,在Dalvik VM中每個線程都有自己的PC和調用棧,方法調用的活動記錄以幀為單位保存在調用棧上。
與JVM版相比,可以發現Dalvik版程序的指令數明顯減少了,數據移動次數也明顯減少了。
ART與Dalvik
Dalvik虛擬機執行的是dex字節碼,解釋執行。從Android 2.2版本開始,支持JIT即時編譯(Just In Time)
在程序運行的過程中進行選擇熱點代碼(經常執行的代碼)進行編譯或者優化。
而ART(Android Runtime) 是在 Android 4.4 中引入的一個開發者選項,也是 Android 5.0 及更高版本的默認 Android 運行時。ART虛擬機執行的是本地機器碼。Android的運行時從Dalvik虛擬機替換成ART虛擬機,并不要求開發者將自己的應用直接編譯成目標機器碼,APK仍然是一個包含dex字節碼的文件。
那么,ART虛擬機執行的本地機器碼是從哪里來?
dex2aot
Dalvik下應用在安裝的過程,會執行一次優化,將dex字節碼進行優化生成odex文件。而Art下將應用的dex字節碼翻譯成本地機器碼的最恰當AOT時機也就發生在應用安裝的時候。ART 引入了預先編譯機制(Ahead Of Time),在安裝時,ART 使用設備自帶的 dex2oat 工具來編譯應用,dex中的字節碼將被編譯成本地機器碼。
這種在安裝時進行編譯的操作勢必會影響應用的安裝速度。
Android N的運作方式
ART 使用預先 (AOT) 編譯,并且從 Android N混合使用AOT編譯,解釋和JIT。
1、最初安裝應用時不進行任何 AOT 編譯(安裝又快了),運行過程中解釋執行,對經常執行的方法進行JIT,經過 JIT 編譯的方法將會記錄到Profile配置文件中。
2、當設備閑置和充電時,編譯守護進程會運行,根據Profile文件對常用代碼進行 AOT 編譯。待下次運行時直接使用。
ClassLoader類加載
任何一個 Java 程序都是由一個或多個 class 文件組成,在程序運行時,需要將 class 文件加載到 JVM 中才可以使 用,負責加載這些 class 文件的就是 Java 的類加載機制。ClassLoader 的作用簡單來說就是加載 class 文件,提供 給程序運行時使用。每個 Class 對象的內部都有一個 classLoader 字段來標識自己是由哪個 ClassLoader 加載的。
class Class { ...
private transient ClassLoader classLoader; ...
}
ClassLoader是一個抽象類,而它的具體實現類主要有:
BootClassLoader
用于加載Android Framework層class文件。
PathClassLoader
用于Android應用程序類加載器。可以加載指定的dex,以及jar、zip、apk中的classes.dex
DexClassLoader
用于加載指定的dex,以及jar、zip、apk中的classes.dex
Log.e(TAG, "Activity.class 由:" + Activity.class.getClassLoader() +" 加載"); Log.e(TAG, "MainActivity.class 由:" + getClassLoader() +" 加載");
//輸出:
Activity.class 由:java.lang.BootClassLoader@d3052a9 加載
MainActivity.class 由:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.enjoy.enjoyfix-1/base.apk"],nativeLibraryDirectories= [/data/app/com.enjoy.enjoyfix-1/lib/x86, /system/lib, /vendor/lib]]] 加載
它們之間的關系如下:
PathClassLoader 與 DexClassLoader 的共同父類是 BaseDexClassLoader 。
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
} }
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent){
super(dexPath, null, librarySearchPath, parent);
} }
可以看到兩者唯一的區別在于:創建 DexClassLoader 需要傳遞一個 optimizedDirectory 參數,并且會將其創建 為 File 對象傳給 super ,而 PathClassLoader 則直接給到null。因此兩者都可以加載指定的dex,以及jar、 zip、apk中的classes.dex
PathClassLoader pathClassLoader = new PathClassLoader("/sdcard/xx.dex", getClassLoader());
File dexOutputDir = context.getCodeCacheDir();
DexClassLoader dexClassLoader = new DexClassLoader("/sdcard/xx.dex",dexOutputDir.getAbsolutePath(), null,getClassLoader());
其實, optimizedDirectory 參數就是dexopt的產出目錄(odex)。那 PathClassLoader 創建時,這個目錄為null,就 意味著不進行dexopt?并不是, optimizedDirectory 為null時的默認路徑為:/data/dalvik-cache。
雙親委托機制
可以看到創建 ClassLoader 需要接收一個 ClassLoader parent 參數。這個 parent 的目的就在于實現類加載的雙 親委托。即:
某個類加載器在接到加載類的請求時,首先將加載任務委托給父類加載器,依次遞歸,如果父類加載器可以完成類 加載任務,就成功返回;只有父類加載器無法完成此加載任務時,才自己去加載。
protected Class> loadClass(String name, boolean resolve) throws
ClassNotFoundException{
// 檢查class是否有被加載
Class c = findLoadedClass(name); if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) { //如果parent不為null,則調用parent的loadClass進行加載
c = parent.loadClass(name, false);
} else { //parent為null,則調用BootClassLoader進行加載
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
// 如果都找不到就自己查找
long t1 = System.nanoTime();
c = findClass(name);
} }
return c; }
1、避免重復加載,當父加載器已經加載了該類的時候,就沒有必要子ClassLoader再加載一次。
2、安全性考慮,防止核心API庫被隨意篡改。
findClass
可以看到在所有父ClassLoader無法加載Class時,則會調用自己的 findClass 方法。 findClass 在ClassLoader
中的定義為:
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
其實任何ClassLoader子類,都可以重寫 loadClass 與 findClass 。一般如果你不想使用雙親委托,則重寫 loadClass 修改其實現。而重寫 findClass 則表示在雙親委托下,父ClassLoader都找不到Class的情況下,定義 自己如何去查找一個Class。而我們的 PathClassLoader 會自己負責加載 MainActivity 這樣的程序中自己編寫的
類,利用雙親委托父ClassLoader加載Framework中的 Activity 。說明 PathClassLoader 并沒有重寫 loadClass ,因此我們可以來看看PathClassLoader中的 findClass 是如何實現的。
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 suppressedExceptions = new ArrayList(); //查找指定的class
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;
實現非常簡單,從 pathList 中查找class。繼續查看 DexPathList
public DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory) {
//.........
// splitDexPath 實現為返回 List.add(dexPath)
// makeDexElements 會去 List.add(dexPath) 中使用DexFile加載dex文件返回 Element數組
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
}
//.........
suppressedExceptions, definingContext);
在DexPathList的構造函數中,通過makeDexElements創建了Element數組,這個數組中包含了Dex文件中的類的信息。
public Class findClass(String name, List suppressed) { //從element中獲得代表Dex的 DexFile
for (Element element : dexElements) { DexFile dex = element.dexFile;
if (dex != null) {
//查找class
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) {
return clazz;
}
} }
if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
通過遍歷Element數組查找需要的類。
熱修復
PathClassLoader 中存在一個Element數組,Element類中存在一個dexFile成員表示dex文件,即:APK中有X個 dex,則Element數組就有X個元素。
在 PathClassLoader 中的Element數組為:[patch.dex , classes.dex , classes2.dex]。如果存在Key.class位于 patch.dex與classes2.dex中都存在一份,當進行類查找時,循環獲得 dexElements 中的DexFile,查找到了 Key.class則立即返回,不會再管后續的element中的DexFile是否能加載到Key.class了。
因此實際上,一種熱修復實現可以將出現Bug的class單獨的制作一份fix.dex文件(補丁包),然后在程序啟動時,從 服務器下載fix.dex保存到某個路徑,再通過fix.dex的文件路徑,用其創建 Element 對象,然后將這個 Element 對 象插入到我們程序的類加載器 PathClassLoader 的 pathList 中的 dexElements 數組頭部。這樣在加載出現Bug的 class時會優先加載fix.dex中的修復類,從而解決Bug。
總結
以上是生活随笔為你收集整理的android虚拟机加载机制,Android虚拟机与类加载机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android gridview ite
- 下一篇: android+ebook控件,Andr