進入Android Dalvik虛擬機之Dalvik虛擬機的特色

Google於2007年末正式發佈了Android SDK,Dalvik虛擬機也第一次進入了人們的視野。它的做者是丹.伯恩斯坦(Dan Bornstein)。Dalvik虛擬機做爲Android平臺的核心組件,擁有以下幾個特色:java

  • 體積小,佔用內存空間小shell

  • 專有的DEX可執行文件格式,體積更小,執行速度更快安全

  • 常量池採用32位索引值,尋址類方法名,字段名,常量更快數據結構

  • 基於寄存器架構,並擁有一套完整的指令系統架構

  • 提供了對象生命週期管理,堆棧管理,線程管理,安全和異常管理以及垃圾回收等重要功能框架

  • 全部的Android程序都運行在Android系統進程裏,每一個進程對應着一個Dalvik虛擬機實例socket

1. Dalvik虛擬機與Java虛擬機的區別函數

Dalvik虛擬機與傳統的Java虛擬機有着許多不一樣點,二者並不兼容,它們顯著的不一樣點主要表如今如下幾個方面:工具

  • Java虛擬機運行的是Java字節碼,Dalvik虛擬機運行的是Dalvik字節碼。傳統的Java程序通過編譯,生成Java字節碼保存在class文件中,Java虛擬機經過解碼class文件中的內容來運行程序。而Dalvik虛擬機運行的是Dalvik字節碼,全部的Dalvik字節碼由Java字節碼轉換而來,並被打包到一個DEX(Dalvik Executable)可執行文件中。Dalvik虛擬機經過解釋DEX文件來執行這些字節碼。測試

  • Dalvik可執行文件體積小。Android SDK中有一個叫dx的工具負責將Java字節碼轉換爲Dalvik字節碼。dx工具對Java類文件從新排列,消除在類文件中出現的全部冗餘信息避免虛擬機在初始化時出現反覆的文件加載與解析過程。通常狀況下,Java類文件中包含多個不一樣的方法簽名,若是其餘的類文件引用該類文件中的方法,方法簽名也會被複制到其類文件中,也就是說,多個不一樣的類會同時包含相同的方法簽名,一樣地,大量的字符串常量在多個類文件中也被重複使用。這些冗餘信息會直接增長文件的體積,同時也會嚴重影響虛擬機解析文件的效率。消除其中的冗餘信息,從新組合造成一個常量池,全部的類文件共享同一個常量池。因爲dx工具對常量池的壓縮,使得相同的字符串,常量在DEX文件中只出現一次,從而減少了文件的體積。

  • Java虛擬機與Dalvik虛擬機架構不一樣。Java虛擬機基於棧架構,程序在運行時虛擬機須要頻繁的從棧上讀取或寫入數據,這個過程須要更多的指令分派與內存訪問次數,會耗費很多CPU時間,對於像手機設備資源有限的設備來講,這是至關大的一筆開銷。Dalvik虛擬機基於寄存器架構。數據的訪問經過寄存器間直接傳遞,這樣的訪問方式比基於棧方式要快不少。測試代碼以下:

package t1;

public class Hello {
    public static void main(String[] args) {
        Hello hello = new Hello();
        System.out.println(hello.foo(5, 3));
    }
    public int foo(int a,int b) {
        return (a + b) * (a - b);
    }
}

將以上內容保存爲Hello.java。打開命令提示符,執行命令:

$ javac -source 1.6 -target 1.6 Hello.java

:若是使用1.7及以上版本的JDK編譯Hello.java,生成Hello.class默認的版本會比較低使用dx生成dex文件會提示class文件無效。解決方法是強制指定class文件的版本。

繼續上面的討論,執行上面的命令生成Hello.class文件。而後執行命令:

./dx --dex --output=t1/Hello.dex t1/Hello.class

執行上面的命令前,命令行進入到dx工具所在的目錄(位於Android SDK的platform-tools目錄中),再把t1/Hello.class目錄與文件copy到dx工具所在的目錄,而後再執行上面的命令生成dex文件。

接下來在Hello.class所在目錄使用javap反編譯Hello.class查看foo()函數的Java字節碼,執行如下命令:

$ javap -c -cp . Hello.class

命令執行後獲得以下代碼(foo()函數部分):

public int foo(int, int);
    Code:
       0: iload_1
       1: iload_2
       2: iadd
       3: iload_1
       4: iload_2
       5: isub
       6: imul
       7: ireturn

下面再使用dexdump(位於Android sdk的platform-tools目錄中)查看foo()函數的Dalvik字節碼,執行如下命令:

$ ./dexdump -d t1/Hello.dex

命令執行後整理輸出結果,能夠獲得以下代碼(foo()函數部分):

000198:                                             |[000198] t1.Hello.foo:(II)I
0001a8: 9000 0304                                   |0000: add-int v0, v3, v4
0001ac: 9101 0304                                   |0002: sub-int v1, v3, v4
0001b0: b210                                        |0004: mul-int/2addr v0, v1
0001b2: 0f00                                        |0005: return v0

查看上面的Java字節碼,發現foo()函數一共佔用了8個字節,代碼中每條指令佔用1個字節。比起Java虛擬機字節碼,上面的Dalvik字節碼顯得簡潔不少,只有4條指令就完成了下面的操做。

經過上面的分析 能夠發現,基於寄存器架構的Dalvik虛擬機與基於棧架構的Java虛擬機相比,因爲生成的代碼指令減小了,程序執行速度會更快一些

2. Dalvik虛擬機是如何執行程序的

Android系統由Linux內核,函數庫,Android運行時,應用程序框架以及應用程序組成。Dalvik虛擬機屬行Android運行時環境,它與一些核心庫共同承擔Android應用程序的運行工做。

Android系統啓動加載完內核後,第一個執行的是init進程,init進程首先要作的是設備的初始化工做,而後讀取inic.rc文件並啓動系統中的重要外部程序 Zygote。Zygote進程是Android全部進程的孵化器進程,它啓動後會首先初始化Dalvik虛擬機,而後啓動system_server並進入Zygote模式,經過socket等候命令。當執行一個Android應用程序時,system_server進程經過Binder IPC方式發送命令給Zygote,Zygote收到命令後經過fork自身建立一個Dalvik虛擬機的實例來執行應用程序的入口函數,這樣一個程序就啓動完成了。

Zygote提供了三種建立進程的方法:

  • fork(),建立一個Zygote進程(這種方式實際不會被調用)

  • forkAndSpecialize(),建立一個非Zygote進程

  • forkSystemServer(),建立一個系統服務進程。

其中,Zygote進程能夠再fork()出其餘進程,非Zygote進程則不能fork其餘進程,而系統服務進程在終止後它的子進程也必終止。當進程fork成功後,執行的工做就交給了Dalvik虛擬機。Dalvik虛擬機首先經過loadClassFromDex()函數完成類的裝載工做,每一個類被成功解析後都會擁有一個ClassObject類型的數據結構存儲在運行時環境中,虛擬機使用gDvm.loadedClasses全局哈希表來存儲與查詢全部裝載進來的類,隨後,字節碼驗證器使用dvmVerifyCodeFlow()函數對裝入的代碼進行校驗,接着虛擬機調用FindCass()函數查找並裝載main方法類,隨後調用dvmInterpret()函數初始化解釋器並執行字節碼流。

3. 關於Dalvik虛擬機JIT(即時編譯)

JIT(Just-in-time Compilation,即時編譯),又稱爲動態編譯,是一種經過在運行時將字節碼翻譯爲機器碼的技術,使得程序的執行速度更快。Android2.2版本系統的Dalvik虛擬機引入了JIT技術,官方宣稱新版的Dalvik虛擬機比以往執行速度快3~6倍。主流的JIT包含兩種字節碼編譯方式

  • method方式:以函數或方法爲單位進行編譯。

  • trace方式:以trace爲單位進行編譯

method方式很好理解,那什麼是trace方式呢?在函數中通常不多是順序執行代碼的,多數的代碼都分紅了好幾條執行路徑,其中函數的有些路徑在實際運行過程當中是不多被執行的,這部分路徑被稱爲「冷路徑」,而執行比較頻繁的路徑被稱爲「熱路徑」。採用傳統的method方式會編譯整個方法的代碼,這會使得在「冷路徑」上浪費不少編譯時間,而且耗費更多的內存;trace方法編譯則可以快速地獲取「熱路徑」代碼,使用更短的時間與更少的內存來編譯代碼

目前,Dalvik虛擬機默認採用trace方式編譯代碼,同時也支持採用method方式來編譯。

相關文章
相關標籤/搜索