一、Dalvik 虛擬機(jī)
Dalvik
是Google
公司自己設(shè)計(jì)用于Android
平臺的Java
虛擬機(jī),它是Android
平臺的重要組成部分,支持dex
格式的Java
應(yīng)用程序的運(yùn)行。
Dalvik
作為面向Linux
、為嵌入式操作系統(tǒng)設(shè)計(jì)的虛擬機(jī),主要負(fù)責(zé)完成 對象生命周期管理、堆棧管理、線程管理、安全和異常管理,以及垃圾回收等。Dalvik
充分利用Linux
進(jìn)程管理的特定,對其進(jìn)行了面向?qū)ο蟮脑O(shè)計(jì),使得可以 同時(shí)運(yùn)行多個(gè)進(jìn)程,而傳統(tǒng)的Java
程序通常只能運(yùn)行一個(gè)進(jìn)程。
1.1 Dalvik 虛擬機(jī)和 JVM 的對比
區(qū)別一
- 大多數(shù)的
JVM
虛擬機(jī)基于 棧的結(jié)構(gòu),基于棧的指令更緊湊,使用的指令只占用一個(gè)字節(jié),因而成為字節(jié)碼。 - 而
Dalvik
虛擬機(jī)則是基于 寄存器,基于寄存器的指令由于需要指定源地址和目標(biāo)地址,因此需要占用更多的指令空間,某些指令需要占用兩個(gè)字節(jié)。
區(qū)別二
-
Java
虛擬機(jī)運(yùn)行的是Java
字節(jié)碼。Java
類會被編譯成一個(gè)或者多個(gè).class
文件,然后打包到jar
文件中,接著Java
虛擬機(jī)會從相應(yīng)的.class
文件和.jar
文件中獲取對應(yīng)的字節(jié)碼。 -
Dalvik
虛擬機(jī)運(yùn)行的是.dex
文件。在Java
類被編譯成.class
文件后,還會通過dx
工具將所有的.class
文件轉(zhuǎn)換一個(gè).dex
文件,Dalvik
虛擬機(jī)再從中讀取指令和數(shù)據(jù)。.dex
文件除了減少整體的文件尺寸和I/O
操作次數(shù),也提高了類的查找速度。
區(qū)別三
-
class
文件中包含多個(gè)不同的方法簽名,如果A
類文件引用B
類文件中的方法,方法簽名也會被復(fù)制到A
類文件中(在虛擬機(jī)加載類的連接階段將會使用該簽名鏈接到B
類的對應(yīng)方法),也就是說,多個(gè)不同的類會同時(shí)包含相同的方法簽名,同樣地,大量的字符串常量在多個(gè)類文件中也被重復(fù)使用,這些冗余信息會直接增加文件的體積,而JVM
在把描述類的數(shù)據(jù)從.class
文件加載到內(nèi)存時(shí),需要對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終才形成可以被虛擬機(jī)直接使用的JAVA
類型,因?yàn)榇罅康娜哂嘈畔ⅲ瑫?yán)重影響虛擬機(jī)解析文件的效率。 - 在
.dex
文件中,由于dx
工具會對JAVA
類文件重新排列,將所有JAVA
類文件中的常量池分解,消除其中的冗余信息,重新組合形成一個(gè)常量池,所有的類文件共享同一個(gè)常量池,使得相同的字符串、常量在.dex
文件中只出現(xiàn)一次,從而減小了文件的體積。
1.2 Dalvik 虛擬機(jī)特點(diǎn)
- 使用
dex
格式的字節(jié)碼,不兼容Java
字節(jié)碼格式 - 代碼密度小,運(yùn)行效率高,節(jié)省資源
- 常量池只使用
32
位的索引 - 有內(nèi)存限制
- 默認(rèn)棧大小是
12KB
- 堆默認(rèn)啟動大小為
2MB
,默認(rèn)最大值為16MB
- 堆支持的最小啟動大小為
1MB
,支持的最大值為1024MB
- 堆和棧參數(shù)可以通過
-Xms
和-Xmx
修改
1.3 Dalvik 系統(tǒng)架構(gòu)
1.3.1 dex 文件結(jié)構(gòu)
.dex
文件結(jié)構(gòu)和.class
文件結(jié)構(gòu)差異的地方很多,但從攜帶的信息上看,.dex
和.class
文件是一致的:
-
header
:存儲了各個(gè)數(shù)據(jù)類型的起始地址、偏移量等信息。 -
proto_ids
:描述函數(shù)原型信息,包括返回值,參數(shù)信息。比如“test:()V”
-
methods_ids
:函數(shù)信息,包括所屬類及對應(yīng)的proto
信息。
雖然.dex
文件的結(jié)構(gòu)很緊湊,但想要運(yùn)行時(shí)的性能得到進(jìn)一步提升,還需要對dex
文件進(jìn)行進(jìn)一步優(yōu)化。優(yōu)化主要針對以下幾個(gè)方面:
- 調(diào)整所有字段的字節(jié)序和對齊結(jié)構(gòu)中的每一個(gè)域
- 驗(yàn)證
.dex
文件中的所有類 - 對一些特定的類進(jìn)行優(yōu)化,對方法里的操作碼進(jìn)行優(yōu)化
.dex
文件經(jīng)過優(yōu)化后文件大小會膨脹,大約增加到原來的1~4
倍。對于內(nèi)置應(yīng)用,一般在系統(tǒng)編譯后,便會生成優(yōu)化文件odex(Optimized dex)
。一個(gè)Android
應(yīng)用程序,需要經(jīng)過以下過程才可以在Dalvik
虛擬機(jī)上運(yùn)行:
- 把
Java
源文件編譯成.class
文件 - 使用
dx
工具把.class
文件轉(zhuǎn)換成.dex
文件 - 使用
aapt
工具把.dex
文件、資源文件以及AndroidManifest.xml
文件組合成APK - 將
APK
安裝到Android
設(shè)備運(yùn)行
1.3.2 Dalvik 類加載器
一個(gè)dex
文件需要類加載器加載原生類和Java
類,然后通過解釋器根據(jù)指令集對Dalvik
字節(jié)碼進(jìn)行解釋和執(zhí)行。Dalvik
類加載器使用mmap
函數(shù),將dex
文件映射到內(nèi)存中,通過普通的內(nèi)存讀取操作即可訪問dex
文件,然后解析dex
文件內(nèi)容并加載其中的類到哈希表中。
解析 dex
總的來說,dex
文件可以抽象為三個(gè)部分:頭部、索引、數(shù)據(jù)。通過頭部可以知道索引的位置和數(shù)目,以及數(shù)據(jù)區(qū)的起始位置。將dex
文件映射到內(nèi)存后,Dalvik
會調(diào)用dexFileParse
函數(shù)對其進(jìn)行分析,分析的結(jié)果放到DexFile
數(shù)據(jù)結(jié)構(gòu)中。DexFile
中的baseAddr
指向映射區(qū)的起始位置,pClassDefs
指向class
索引的起始位置。為了加快class
的查找速度,還創(chuàng)建一個(gè)哈希表,對class
名字進(jìn)行哈希并生成索引。
加載 class
解析工作完成后就進(jìn)行class的加載,加載的類需要用ClassObject
數(shù)據(jù)結(jié)構(gòu)來存儲。
typedef struct Object {
ClassObject* clazz; // 類型對象
Lock lock; // 鎖對象
} Object;
其中clazz
指向ClassObjec
t對象,還包含一個(gè)Lock
對象。如果其它線程想要獲取它的鎖,只有等這個(gè)線程釋放。Dalvik
每加載一個(gè)class
都會對應(yīng)一個(gè)ClassObject
對象,加載過程會在內(nèi)存中分配幾個(gè)區(qū)域,分別存放directMethod
、virtualMethod
、sfield
、ifield
。這些信息從dex
文件的數(shù)據(jù)區(qū)中讀取。字段Field
的定義如下:
struct Field {
ClassObject* clazz; //所屬類型
const char* name; // 變量名稱
const char* signature; // 如“Landroid/os/Debug;”
u4 accessFlags; // 訪問標(biāo)記
#ifdef PROFILE_FIELD_ACCESS
u4 gets;
u4 puts;
#endif
};
待得到class
索引后,實(shí)際的加載由loadClassFromDex
來完成。首先它會讀取class
的具體數(shù)據(jù),分別加載directMethod
、virtualMethod
、ifield
和sfield
,然后為ClassObject
數(shù)據(jù)結(jié)構(gòu)分配內(nèi)存,并讀取dex
文件的相關(guān)信息。加載完成后,將加載的class
通過dvmAddClassToHash
函數(shù)放入哈希表,以方便下次查找;最后,通過dvmLinkClass
查找該類的超類,如果有接口類則加載相應(yīng)的接口類。
1.3.3 Dalvik 解釋器
對于任何虛擬機(jī)來說,解釋器無疑是核心的部分,所有的Java
字節(jié)碼都經(jīng)過解釋器解釋執(zhí)行。由于Dalvik
解釋器的效率很重要,Android
分別實(shí)現(xiàn)了C
語言版和各種匯編語言版的解釋器。解釋器通常是循環(huán)執(zhí)行,需要一個(gè)入口函數(shù)調(diào)用處理程序執(zhí)行第一條指令,而后每條指令執(zhí)行時(shí)引出下一條指令,通過函數(shù)指針調(diào)用處理程序。
二、Dalvik 虛擬機(jī)和 ART 虛擬機(jī)對比
在Android 4.4
之后,Google
開始使用了更加優(yōu)秀的ART
虛擬機(jī)來替換Dalvik
虛擬機(jī),下面我們就來對比一下這兩者之間的區(qū)別。
2.1 Dalvik
在 打包的過程中 會先將.java
等源碼通過javac
編譯成.class
文件,再通過dx
將.class
文件轉(zhuǎn)換成Dalvik
虛擬機(jī)執(zhí)行的.dex
文件。
在 應(yīng)用啟動的時(shí)候 先將.dex
文件 轉(zhuǎn)換成機(jī)器碼,又因?yàn)?code>65536的文件,導(dǎo)致在應(yīng)用冷啟動的時(shí)候有一個(gè)合包的過程,最后的結(jié)果就是app
的啟動時(shí)間有可能變慢,這就是Dalvik
虛擬機(jī)的JIT(Just in Time)
特性。
2.2 ART
ART
除了兼容了Dalvik
虛擬機(jī)的特性之外,還有一個(gè)很好的特性AOT(Ahead of Time)
,這個(gè)特性就是把 .dex 文件轉(zhuǎn)換成機(jī)器碼 這個(gè)步驟提前到了 應(yīng)用安裝 的時(shí)候,ART
虛擬機(jī)將.dex
文件轉(zhuǎn)換成可直接運(yùn)行的.oat
文件,ART
虛擬機(jī)天生支持多dex
,所以也不會有一個(gè)合包的過程,因此會極大的提升APP
冷啟動速度。
2.3 ART 虛擬機(jī)的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 加快
APP
冷啟動速度 - 提升
GC
速度 - 提供功能全面的
Debug
特性
缺點(diǎn):
-
APP
安裝速度慢,因?yàn)樵?code>APK安裝的時(shí)候要生成可運(yùn)行.oat
文件 -
APK
占用空間大,因?yàn)樵?code>APK安裝的時(shí)候要生成可運(yùn)行.oat
文件
三、參考文獻(xiàn)
理解 Android 虛擬機(jī)體系結(jié)構(gòu)
Android Dalvik 虛擬機(jī)和 ART 虛擬機(jī)對比
Dalvik 虛擬機(jī)簡要介紹和學(xué)習(xí)計(jì)劃
Dalvik 虛擬機(jī)的啟動過程分析
Dalvik 虛擬機(jī)的運(yùn)行過程分析
深入理解 Dalvik 虛擬機(jī) - Android應(yīng)用進(jìn)程啟動過程分析
深入理解 ART 虛擬機(jī) - 虛擬機(jī)的啟動
深入理解 ART 虛擬機(jī) - ART 的函數(shù)運(yùn)行機(jī)制
深入理解 Dalvik 虛擬機(jī) - 解釋器的運(yùn)行機(jī)制
深入理解ART虛擬機(jī) - ImageSpace的加載過程分析
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:http://www.lxweimin.com/p/fd82d18994ce
- Android 面試文檔分享:http://www.lxweimin.com/p/8456fe6b27c4