Dalvik是Google公司本身設計用於Android平臺的Java虛擬機,它是Android平臺的重要組成部分,支持dex格式(Dalvik Executable)的Java應用程序的運行。dex格式是專門爲Dalvik設計的一種壓縮格式,適合內存和處理器速度有限的系統。Google對其進行了特定的優化,使得Dalvik具備高效、簡潔、節省資源的特色。從Android系統架構圖知,Dalvik虛擬機運行在Android的運行時庫層。linux
Dalvik做爲面向Linux、爲嵌入式操做系統設計的虛擬機,主要負責完成對象生命週期管理、堆棧管理、線程管理、安全和異常管理,以及垃圾回收等。Dalvik充分利用Linux進程管理的特定,對其進行了面向對象的設計,使得能夠同時運行多個進程,而傳統的Java程序一般只能運行一個進程,這也是爲何Android不採用JVM的緣由。Dalvik爲了達到優化的目的,底層的操做大多和系統內核相關,或者直接調用內核接口。另外,Dalvik早期並無JIT編譯器,直到Android2.2才加入了對JIT的技術支持。android
本質上,Dalvik也是一個Java虛擬機。但它特別之處在於沒有使用JVM規範。大多數Java虛擬機都是基於棧的結構,而Dalvik虛擬機則是基於寄存器。基於棧的指令很緊湊,例如,Java虛擬機使用的指令只佔一個字節,於是稱爲字節碼。基於寄存器的指令因爲須要指定源地址和目標地址,所以須要佔用更多的指令空間。Dalvik虛擬機的某些指令須要佔用兩個字節。基於棧和基於寄存器的指令集各有優劣,通常而言,執行一樣的功能,前者須要更多的指令(主要是load和store指令),然後者須要更多的指令空間。須要更多指令意味着要多佔用CPU時間,而須要更多指令空間意味着數據緩衝(d-cache)更易失效。算法
Java虛擬機運行的是Java字節碼,而Dalvik虛擬機運行的是專有文件格式dex。在Java程序中,Java類會被編譯成一個或多個class文件,而後打包到jar文件中,接着Java虛擬機會從相應的class文件和jar文件中獲取對應的字節碼。Android應用雖然也使用Java語言,可是在編譯成class文件後,還會經過DEX工具將全部的class文件轉換成一個dex文件,Dalvik虛擬機再從中讀取指令和數據。dex文件除了減小總體的文件尺寸和I/O操做次數,也提升了類的查找速度。 數組
由下圖能夠看到,jar和apk文件的組成結構,以及class文件和dex文件的差別。dex格式文件使用共享的、特定類型的常量池機制來節省內存。常量池存儲類中的全部字面常量,它包括字符串常量、字段常量等值。 安全
總的來講,Dalvik虛擬機具備如下特色:bash
實際上,Dalvik是基於Apache Harmony(Apache軟件基金會的Java SE項目)的部分實現,提供了本身的一套庫,即上層Java應用程序編寫所使用的API 數據結構
Apache Harmony大致上分爲三個層:操做系統、Java虛擬機、Java類庫。它的特色在於虛擬機和類庫內部被高度模塊化,每個模塊都有必定的接口定義。操做系統層與虛擬機層之間的接口由Portability Layer定義,它封裝了不一樣操做系統的差別,爲虛擬機和類庫的本地代碼提供了一套統一的API訪問底層系統調用。虛擬機與類庫之間的接口除了Java規範定義的JNI、JVMITI外,還加入了一層虛擬機接口,由內核類和本地代碼組成。實現了虛擬機接口的虛擬機均可以使用Harmony的類庫實現,而且能夠被Harmony提供的同一個Java啓動程序啓動架構
下面是Dalvik虛擬機的結構圖: 模塊化
一個應用首先通過DX工具將class文件轉換成Dalvik虛擬機能夠執行的dex文件,而後由類加載器加載原生類和Java類,接着由解釋器根據指令集對Dalvik字節碼進行解釋、執行。最後,根據dvm_arch參數選擇編譯的目標機體系結構。函數
dex文件結構和class文件結構差別的地方不少,但從攜帶的信息上看,dex和class文件是一致的。
優化主要針對如下幾個方面:
dex文件通過優化後文件大小會膨脹,大約增長到原來的1~4倍。對於內置應用,通常在系統編譯後,便會生成優化文件(odex: Optimized dex)。一個Android應用程序,須要通過如下過程才能夠在Dalvik虛擬機上運行:
一個dex文件須要類加載器加載原生類和Java類,而後經過解釋器根據指令集對Dalvik字節碼進行解釋和執行。Dalvik類加載器使用mmap函數,將dex文件映射到內存中,經過普通的內存讀取操做便可訪問dex文件,而後解析dex文件內容並加載其中的類到哈希表中。
總的來講,dex文件能夠抽象爲三個部分:頭部、索引、數據。經過頭部能夠知道索引的位置和數目,以及數據區的起始位置。將dex文件映射到內存後,Dalvik會調用dexFileParse函數對其進行分析,分析的結果放到DexFile數據結構中。DexFile中的baseAddr指向映射區的起始位置,pClassDefs指向class索引的起始位置。爲了加快class的查找速度,還建立一個哈希表,對class名字進行哈希並生成索引。
解析工做完成後就進行class的加載,加載的類須要用ClassObject數據結構來存儲。
typedef struct Object {
ClassObject* clazz; // 類型對象
Lock lock; // 鎖對象
} Object;
複製代碼
其中clazz指向ClassObject對象,還包含一個Lock對象。若是其它線程想要獲取它的鎖,只有等這個線程釋放。Dalvik每加載一個class都會對應一個ClassObject對象,加載過程會在內存中分配幾個區域,分別存放directMethod, virtualMethod, sfield, ifield。這些信息從dex文件的數據區中讀取。字段Field的定義以下:
struct Field {
ClassObject* clazz; //所屬類型
const char* name; // 變量名稱
const char* signature; // 如「Landroid/os/Debug;」
u4 accessFlags; // 訪問標記
#ifdef PROFILE_FIELD_ACCESS
u4 gets;
u4 puts;
#endif
};
複製代碼
待獲得class索引後,實際的加載由loadClassFromDex來完成。首先它會讀取class的具體數據,分別加載directMethod, virtualMethod, ifield和sfield,而後爲ClassObject數據結構分配內存,並讀取dex文件的相關信息。加載完成後,將加載的class經過dvmAddClassToHash函數放入哈希表,以方便下次查找;最後,經過dvmLinkClass查找該類的超類,若是有接口類則加載相應的接口類。
對於任何虛擬機來講,解釋器無疑是核心的部分,全部的Java字節碼都通過解釋器解釋執行。因爲Dalvik解釋器的效率很重要,Android分別實現了C語言版和各類彙編語言版的解釋器。解釋器一般是循環執行,須要一個入口函數調用處理程序執行第一條指令,然後每條指令執行時引出下一條指令,經過函數指針調用處理程序。
垃圾收集是Dalvik虛擬機內存管理的核心。此處只介紹Dalvik虛擬機的垃圾收集功能。垃圾收集的性能在很大程度上影響了一個Java程序內存使用的效率。Dalvik虛擬機使用經常使用的Mark-Sweep算法,該算法分Mark階段(標記出活動對象)、Sweep階段(回收垃圾內存)和可選的Compact階段(減小堆中的碎片)
垃圾收集的第一步是標記出活動對象,由於沒有辦法識別那些不可訪問的對象,這樣全部未被標記的對象就是能夠回收的垃圾。當進行垃圾收集時,須要中止Dalvik虛擬機的運行(除垃圾收集外),所以垃圾收集又被稱做STW(stop-the-world)。Dalvik虛擬機在運行過程當中要維護一些狀態信息,這些信息包括:每一個線程所保存的寄存器、Java類中的靜態字段、局部和全局的JNI引用,JVM中的全部函數調用會對應一個相應C的棧幀。每個棧幀裏可能包含對對象的引用,好比包含對象引用的局部變量和參數。全部這些引用信息被加入到一個根集合中,而後從根集合開始,遞歸查找能夠從根集合出發訪問的對象。所以,Mark過程又叫作追蹤,追蹤全部可被訪問的對象。
垃圾收集的第二步就是回收內存。在Mark階段經過markBits位圖能夠獲得全部可訪問的對象集合,而liveBits位圖表示全部已經分配的對象集合。經過比較liveBits位圖和markBits位圖的差別就是全部可回收的對象集合。Sweep階段調用free來釋放這些內存給堆。
在底層內存實現上,Android系統使用的是msspace,這是一個輕量級的malloc實現。除了建立和初始化用於存儲普通Java對象的內存堆,Android還建立三個額外的內存堆:
Dalvik進程管理是依賴於linux的進程體系結構的,如要爲應用程序建立一個進程,它會使用linux的fork機制來複制一個進程。Zygote是一個虛擬機進程,同時也是一個虛擬機實例的孵化器,它經過init進程啓動。此處分析Dalvik虛擬機啓動的相關過程。
AndroidRuntime類主要作了如下幾件事情:
在JNI中,dvmCreateJNIEnv爲當前線程建立和初始化一個JNI環境,即一個JNIEnvExt對象。最後調用dvmStartup來初始化前面建立的Dalvik虛擬機實例。函數dvmInitZygote調用了系統的setpgid來設置當前進程,即Zygote進程的進程組ID。這一步完成後,Dalvik虛擬機的建立和初始化工做就完成了。