日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

android虚拟机加载机制,Android虚拟机与类加载机制

發(fā)布時(shí)間:2024/9/19 Android 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android虚拟机加载机制,Android虚拟机与类加载机制 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

JVM與Dalvik

Android應(yīng)用程序運(yùn)行在Dalvik/ART虛擬機(jī),并且每一個(gè)應(yīng)用程序?qū)?yīng)有一個(gè)單獨(dú)的Dalvik虛擬機(jī)實(shí)例。Dalvik虛擬機(jī)實(shí)則也算是一個(gè)Java虛擬機(jī),只不過(guò)它執(zhí)行的不是class文件,而是dex文件。Dalvik虛擬機(jī)與Java虛擬機(jī)共享有差不多的特性,差別在于兩者執(zhí)行的指令集是不一樣的,前者的指令集是基本寄存器的,而后者的指令集是基于堆棧的。

那什么是基于棧的虛擬機(jī),什么又是基于寄存器的虛擬機(jī)?

基于棧的虛擬機(jī)

對(duì)于基于棧的虛擬機(jī)來(lái)說(shuō),每一個(gè)運(yùn)行時(shí)的線程,都有一個(gè)獨(dú)立的棧。棧中記錄了方法調(diào)用的歷史,每有一次方法調(diào)用,棧中便會(huì)多一個(gè)棧楨。最頂部的棧楨稱(chēng)作當(dāng)前棧楨,其代表著當(dāng)前執(zhí)行的方法。基于棧的虛擬機(jī)通過(guò)操作數(shù)棧進(jìn)行所有操作。

字節(jié)碼指令

ICONST_1 : 將int類(lèi)型常量1壓入操作數(shù)棧;

ISTORE 0 : 將棧頂int類(lèi)型值存入局部變量0;

IADD : 執(zhí)行int類(lèi)型的加法 ;

基于棧的虛擬機(jī)在執(zhí)行方法時(shí),需要將數(shù)據(jù)讀取到操作數(shù)棧,然后再將數(shù)據(jù)寫(xiě)入棧幀中的局部變量表等操作。

執(zhí)行過(guò)程

(0)PC計(jì)數(shù)器指向0行,執(zhí)行ICONST_1指令,將常量1壓入操作數(shù)棧中

(1)執(zhí)行ISTORE_0指令,將操作數(shù)棧的棧頂元素存儲(chǔ)到局部變量表的第1個(gè)位置

(2)執(zhí)行ICONST_2指令,加載數(shù)字2到操作數(shù)棧

(3)執(zhí)行ISTORE_1將操作數(shù)棧中的數(shù)據(jù)2存儲(chǔ)到局部變量表的第2個(gè)位置

(4)執(zhí)行ILOAD 0將局部變量表中的第1個(gè)位置的數(shù)字加載到操作數(shù)棧中

(5)執(zhí)行ILOAD 1將局部變量表中的第2個(gè)位置的數(shù)字加載到操作數(shù)棧中

(6)執(zhí)行IADD 將操作數(shù)棧的棧頂?shù)膬蓚€(gè)數(shù)相加,并將所得結(jié)果壓如棧頂

(7)將操作數(shù)棧頂?shù)臄?shù)據(jù)存儲(chǔ)到局部變量表的第3個(gè)位置

(8)執(zhí)行RETUREN指令,方法結(jié)束。

基于寄存器的虛擬機(jī)

寄存器是CPU的組成部分。寄存器是有限存貯容量的高速存貯部件,它們可用來(lái)暫存指令、數(shù)據(jù)和位址

寄存器

基于寄存器的虛擬機(jī)中沒(méi)有操作數(shù)棧,但是有很多虛擬寄存器。其實(shí)和操作數(shù)棧相同,這些寄存器也存放在運(yùn)行時(shí)棧中,本質(zhì)上就是一個(gè)數(shù)組。與JVM相似,在Dalvik VM中每個(gè)線程都有自己的PC和調(diào)用棧,方法調(diào)用的活動(dòng)記錄以幀為單位保存在調(diào)用棧上。

與JVM版相比,可以發(fā)現(xiàn)Dalvik版程序的指令數(shù)明顯減少了,數(shù)據(jù)移動(dòng)次數(shù)也明顯減少了。

ART與Dalvik

Dalvik虛擬機(jī)執(zhí)行的是dex字節(jié)碼,解釋執(zhí)行。從Android 2.2版本開(kāi)始,支持JIT即時(shí)編譯(Just In Time)

在程序運(yùn)行的過(guò)程中進(jìn)行選擇熱點(diǎn)代碼(經(jīng)常執(zhí)行的代碼)進(jìn)行編譯或者優(yōu)化。

而ART(Android Runtime) 是在 Android 4.4 中引入的一個(gè)開(kāi)發(fā)者選項(xiàng),也是 Android 5.0 及更高版本的默認(rèn) Android 運(yùn)行時(shí)。ART虛擬機(jī)執(zhí)行的是本地機(jī)器碼。Android的運(yùn)行時(shí)從Dalvik虛擬機(jī)替換成ART虛擬機(jī),并不要求開(kāi)發(fā)者將自己的應(yīng)用直接編譯成目標(biāo)機(jī)器碼,APK仍然是一個(gè)包含dex字節(jié)碼的文件。

那么,ART虛擬機(jī)執(zhí)行的本地機(jī)器碼是從哪里來(lái)?

dex2aot

Dalvik下應(yīng)用在安裝的過(guò)程,會(huì)執(zhí)行一次優(yōu)化,將dex字節(jié)碼進(jìn)行優(yōu)化生成odex文件。而Art下將應(yīng)用的dex字節(jié)碼翻譯成本地機(jī)器碼的最恰當(dāng)AOT時(shí)機(jī)也就發(fā)生在應(yīng)用安裝的時(shí)候。ART 引入了預(yù)先編譯機(jī)制(Ahead Of Time),在安裝時(shí),ART 使用設(shè)備自帶的 dex2oat 工具來(lái)編譯應(yīng)用,dex中的字節(jié)碼將被編譯成本地機(jī)器碼。

這種在安裝時(shí)進(jìn)行編譯的操作勢(shì)必會(huì)影響應(yīng)用的安裝速度。

Android N的運(yùn)作方式

ART 使用預(yù)先 (AOT) 編譯,并且從 Android N混合使用AOT編譯,解釋和JIT。

1、最初安裝應(yīng)用時(shí)不進(jìn)行任何 AOT 編譯(安裝又快了),運(yùn)行過(guò)程中解釋執(zhí)行,對(duì)經(jīng)常執(zhí)行的方法進(jìn)行JIT,經(jīng)過(guò) JIT 編譯的方法將會(huì)記錄到Profile配置文件中。

2、當(dāng)設(shè)備閑置和充電時(shí),編譯守護(hù)進(jìn)程會(huì)運(yùn)行,根據(jù)Profile文件對(duì)常用代碼進(jìn)行 AOT 編譯。待下次運(yùn)行時(shí)直接使用。

ClassLoader類(lèi)加載

任何一個(gè) Java 程序都是由一個(gè)或多個(gè) class 文件組成,在程序運(yùn)行時(shí),需要將 class 文件加載到 JVM 中才可以使 用,負(fù)責(zé)加載這些 class 文件的就是 Java 的類(lèi)加載機(jī)制。ClassLoader 的作用簡(jiǎn)單來(lái)說(shuō)就是加載 class 文件,提供 給程序運(yùn)行時(shí)使用。每個(gè) Class 對(duì)象的內(nèi)部都有一個(gè) classLoader 字段來(lái)標(biāo)識(shí)自己是由哪個(gè) ClassLoader 加載的。

class Class { ...

private transient ClassLoader classLoader; ...

}

ClassLoader是一個(gè)抽象類(lèi),而它的具體實(shí)現(xiàn)類(lèi)主要有:

BootClassLoader

用于加載Android Framework層class文件。

PathClassLoader

用于Android應(yīng)用程序類(lèi)加載器??梢约虞d指定的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]]] 加載

它們之間的關(guān)系如下:

PathClassLoader 與 DexClassLoader 的共同父類(lèi)是 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);

} }

可以看到兩者唯一的區(qū)別在于:創(chuàng)建 DexClassLoader 需要傳遞一個(gè) optimizedDirectory 參數(shù),并且會(huì)將其創(chuàng)建 為 File 對(duì)象傳給 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());

其實(shí), optimizedDirectory 參數(shù)就是dexopt的產(chǎn)出目錄(odex)。那 PathClassLoader 創(chuàng)建時(shí),這個(gè)目錄為null,就 意味著不進(jìn)行dexopt?并不是, optimizedDirectory 為null時(shí)的默認(rèn)路徑為:/data/dalvik-cache。

雙親委托機(jī)制

可以看到創(chuàng)建 ClassLoader 需要接收一個(gè) ClassLoader parent 參數(shù)。這個(gè) parent 的目的就在于實(shí)現(xiàn)類(lèi)加載的雙 親委托。即:

某個(gè)類(lèi)加載器在接到加載類(lèi)的請(qǐng)求時(shí),首先將加載任務(wù)委托給父類(lèi)加載器,依次遞歸,如果父類(lèi)加載器可以完成類(lèi) 加載任務(wù),就成功返回;只有父類(lèi)加載器無(wú)法完成此加載任務(wù)時(shí),才自己去加載。

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,則調(diào)用parent的loadClass進(jìn)行加載

c = parent.loadClass(name, false);

} else { //parent為null,則調(diào)用BootClassLoader進(jìn)行加載

c = findBootstrapClassOrNull(name);

}

} catch (ClassNotFoundException e) {

}

if (c == null) {

// 如果都找不到就自己查找

long t1 = System.nanoTime();

c = findClass(name);

} }

return c; }

1、避免重復(fù)加載,當(dāng)父加載器已經(jīng)加載了該類(lèi)的時(shí)候,就沒(méi)有必要子ClassLoader再加載一次。

2、安全性考慮,防止核心API庫(kù)被隨意篡改。

findClass

可以看到在所有父ClassLoader無(wú)法加載Class時(shí),則會(huì)調(diào)用自己的 findClass 方法。 findClass 在ClassLoader

中的定義為:

protected Class> findClass(String name) throws ClassNotFoundException {

throw new ClassNotFoundException(name);

}

其實(shí)任何ClassLoader子類(lèi),都可以重寫(xiě) loadClass 與 findClass 。一般如果你不想使用雙親委托,則重寫(xiě) loadClass 修改其實(shí)現(xiàn)。而重寫(xiě) findClass 則表示在雙親委托下,父ClassLoader都找不到Class的情況下,定義 自己如何去查找一個(gè)Class。而我們的 PathClassLoader 會(huì)自己負(fù)責(zé)加載 MainActivity 這樣的程序中自己編寫(xiě)的

類(lèi),利用雙親委托父ClassLoader加載Framework中的 Activity 。說(shuō)明 PathClassLoader 并沒(méi)有重寫(xiě) loadClass ,因此我們可以來(lái)看看PathClassLoader中的 findClass 是如何實(shí)現(xiàn)的。

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;

實(shí)現(xiàn)非常簡(jiǎn)單,從 pathList 中查找class。繼續(xù)查看 DexPathList

public DexPathList(ClassLoader definingContext, String dexPath,

String librarySearchPath, File optimizedDirectory) {

//.........

// splitDexPath 實(shí)現(xiàn)為返回 List.add(dexPath)

// makeDexElements 會(huì)去 List.add(dexPath) 中使用DexFile加載dex文件返回 Element數(shù)組

this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,

}

//.........

suppressedExceptions, definingContext);

在DexPathList的構(gòu)造函數(shù)中,通過(guò)makeDexElements創(chuàng)建了Element數(shù)組,這個(gè)數(shù)組中包含了Dex文件中的類(lèi)的信息。

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;

}

通過(guò)遍歷Element數(shù)組查找需要的類(lèi)。

熱修復(fù)

PathClassLoader 中存在一個(gè)Element數(shù)組,Element類(lèi)中存在一個(gè)dexFile成員表示dex文件,即:APK中有X個(gè) dex,則Element數(shù)組就有X個(gè)元素。

在 PathClassLoader 中的Element數(shù)組為:[patch.dex , classes.dex , classes2.dex]。如果存在Key.class位于 patch.dex與classes2.dex中都存在一份,當(dāng)進(jìn)行類(lèi)查找時(shí),循環(huán)獲得 dexElements 中的DexFile,查找到了 Key.class則立即返回,不會(huì)再管后續(xù)的element中的DexFile是否能加載到Key.class了。

因此實(shí)際上,一種熱修復(fù)實(shí)現(xiàn)可以將出現(xiàn)Bug的class單獨(dú)的制作一份fix.dex文件(補(bǔ)丁包),然后在程序啟動(dòng)時(shí),從 服務(wù)器下載fix.dex保存到某個(gè)路徑,再通過(guò)fix.dex的文件路徑,用其創(chuàng)建 Element 對(duì)象,然后將這個(gè) Element 對(duì) 象插入到我們程序的類(lèi)加載器 PathClassLoader 的 pathList 中的 dexElements 數(shù)組頭部。這樣在加載出現(xiàn)Bug的 class時(shí)會(huì)優(yōu)先加載fix.dex中的修復(fù)類(lèi),從而解決Bug。

總結(jié)

以上是生活随笔為你收集整理的android虚拟机加载机制,Android虚拟机与类加载机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。