Java&Android 基礎(chǔ)知識梳理(7) - Android 虛擬機(jī)

一、Dalvik 虛擬機(jī)

DalvikGoogle公司自己設(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)

.class 文件與 .dex 文件對比

.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)行
.apk 文件的產(chǎ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指向ClassObject對象,還包含一個(gè)Lock對象。如果其它線程想要獲取它的鎖,只有等這個(gè)線程釋放。Dalvik每加載一個(gè)class都會對應(yīng)一個(gè)ClassObject對象,加載過程會在內(nèi)存中分配幾個(gè)區(qū)域,分別存放directMethodvirtualMethodsfieldifield。這些信息從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ù),分別加載directMethodvirtualMethodifieldsfield,然后為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 知識梳理系列:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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