虛擬機二三事 - Part 3. Java 虛擬機和 Dalvik 虛擬機的差異

6/26/2021 AndroidVirtual MachineJIT compilerCompiler

很多人可能會覺得 JVM 跟 DVM 是相似的虛擬機,因為 Java Code 可以跑在 JVM 也可以跑在 DVM。然而,兩者差異甚巨,本篇就來介紹兩者的不同吧😉

哇,這張圖就說完了,我後面來要講什麼🤣

# 一、 JVM 跟 DVM 的差異比較

這是兩者之差異的表格

差異項目 Java VM Delvik VM
架構圖
開發公司 Sun(被 Oracle 收購) Google
虛擬機可執行文件格式 .class .dex (Android 為 DVM 做的最佳化)
可以執行.class ? 可以 不行,要使用 dex compiler 轉成 .dex 格式才行
虛擬機模型 stack-based register-based
使用環境 PC mobile device
記憶體使用量
虛擬機的實例 所有應用程式共享一個 JVM 實例 每一個應用程式都有一個自己的 DVM 實例
作業系統 支援 Linux, Windows, MacOS 只支援 Android OS
最終打包的執行檔 JAR APK

一旦你準備好 .class 文件,您就可以將此文件提供給任何平台,它會將其轉換為本地機器程式碼。然而,Android 設備上為了提高執行效率,在編譯Android 項目時,Android 通過SDK 提供的工具 dex.jar(dex compiler) 會把所有的.class 文件最終打包成數個 .dex 文件。

另外值得一提的是同一台設備中,JVM只能執行一個實例,也就是說這台設備的所有應用都執行在同一個JVM中,而DVM是經過最佳化的,它在同一台設備可以執行多個實例,每一款應用執行起來都會執行單獨的虛擬機(DVM),執行在單獨一個行程中。

# 二、從 Class 到 Dex 的過程

# 2-1. 被壓縮的 .Dex 文件

從上圖可以看出,除了最後兩個步驟外,一切都與 JVM 相同。Dex 編譯器將類文件轉換為在 Dalvik VM 上執行的 .dex 文件。多個class文件轉換為一個dex文件, Dex 是一個更進一步最佳化的檔案格式,因此將多個 .class 轉成 .dex 檔,除了將 Bytecode 轉成 DVM 的指令外,也去除了大量冗餘,節省了很多儲存空間。 在來自相同 .class 的情形下,一個未被壓縮的 .dex 仍然比被壓縮後打包的 JAR (Java archive) 還小

# 2-2. Dex Compiler

Android第一版發佈時候使用的是JDK6,即只支持Java6的Bytecode Instruction。但是到現在Java已經發展到Java13了,期間加了新的Bytecode,增加了好多新的語言特性,以及新的API,Android 生態系統總不能一直讓開發者使用 Java6來開發吧,那樣估計開發者要起來反抗了?所以Google的想辦法支持Java7、8、9...

我們先將 java 的 .class 檔進行 desugar 的動作,desugar 來自語法糖,主要是因應後期 Java 語言新特性 (lambda, for loop 等),有許多對開發者友善的語法糖。但是編譯的時候就需要脫糖。脫糖的目的有兩種

  • Java 新語言特性的支持

就是新版本Java 引入的語言特性。例如: lambda、方法的使用等,這些是被最先支持的。支持的方式是通過將這些語言特性還原為對應的老式寫法。

  • Java 新語言Api的支持

對新版本API的支持。例如Java 8 新引入的java.util.stream 已經新的時間api java.time。這個不好弄了,因為老版本Android攜帶的jre根本沒有這套東西,怎麼辦呢?

編譯器(D8/R8)幫你實現一套,然後打包成.dex文件加到你的apk中,然後讓你的程式碼使用這裡面的實現。

之後使用開源實用程式 ProGuard (opens new window) 縮小生成的Bytecode,在用 Dex compiler 更進一步編譯。得到更小的 .dex 檔案。

# 2-2. Dex 第一次進化 - D8 Compiler

最初,使用內置的 DX 編譯器將.class文件轉換為.dex。但是從 Android Studio 3.1開始,默認編譯器是D8,D8全稱是Dope8。與 DX 編譯器相比,D8 編譯速度更快,輸出的 .dex 文件更小,在執行時提升更好的效能。 可以明顯看到,以前脫糖這一個過程是作為編譯的一個單獨步驟進行的,編譯為.class後就是脫糖,脫糖後執行 ProGuard,然後再編譯為.dex文件。D8將脫糖和編譯為.dex文件這兩步合併為一步來執行了。D8脫糖後的 Bytecode精確度和執行效率都更高。

# 2-3. Dex 第二次進化 - R8 Compiler

Java生態一般使用ProGuard對Bytecode進行最佳化,而R8將ProGuard這一步的功能給整合到了Dalvik 生成環節中了,不管是ProGuard還是R8主要從下面幾個方面進行最佳化:

  • 收縮(shrinking):去掉沒有使用的類,方法,字段等等。
  • 混淆(obfuscation):縮短類和成員的名稱,從而減小DEX文件的大小。
  • 程式碼最佳化(optimization):從指令層面上最佳化,例如指令重排等。檢查並重寫程式碼,以進一步減小應用的DEX文件的大小。

更多細節請參照 Android 官方網站 - 縮減、混淆處理和優化應用 (opens new window)

所以由上表,可以看出 R8 幫我們程式碼做瘦身。

這裡只簡單介紹從 class 到 dex 經過了哪些操作及演進,相關細節以後有時間會談🤣。

# 三、為何 Android 不直接用 JVM 而要用 DVM、ART ?

# 3-1. DVM 相對於 JVM 的優勢

由於Androd 執行在移動設備上,記憶體以及電量等諸多方面跟一般的 PC 設備都有本質的區別,一般的 JVM 沒法滿足移動設備的要求,太吃資源、太笨重了,所以在開發 Android 過程中,Android 團隊一開始就必須打造一個符合移動設備的可以執行 Java 的虛擬機。

Dalvik 經過最佳化,變得很輕巧。執行跟載入的速度變得很快,允許在有限的記憶體中同時執行多個虛擬機的實例,並且每一個 Dalvik 應用作為一個獨立的Linux 行程執行。獨立的進程可以防止在虛擬機崩潰的時候所有程序都被關閉。每一個應用程式都有自己的虛擬機。

除了上面提到的,還有以下幾點。

  1. Dalvik 負責進程隔離和線程管理,每一個Android應用在底層都會對應一個獨立的Dalvik虛擬機實例,其程式碼在虛擬機的解釋下得以執行。

  2. dex 文件格式可以減少整體文件尺寸,提高I/O操作的類查找速度。

  3. 有一個特殊的虛擬機行程 Zygote ,他是虛擬機實例的孵化器。它在系統啟動的時候就會產生,它會完成虛擬機的初始化、庫的加載、預製類庫和初始化的操作。如果系統需要一個新的虛擬機實例,它會迅速復制自身,以最快的速度提供給系統。

  4. 虛擬機的暫存器架構模型本身就不同。JVM 是 stack-based VM 而 DVM 是 register-based,執行效率較高。

# 3-2. Oracle 跟 Google 的紛爭 - API到底該不該有版權保護

JVM 是 GPL License,而 Android 是 Apache License。

# 3-3. Google 想要所有的流程都自己掌握

如果直接使用 JVM 的話,可能又有版權問題,Google 開發團隊想要從頭到尾自己開發,在最佳化的工作上才能完全掌握。

其實最主要是第一點,JVM太笨重不適合放在硬體資源有限的行動裝置,且大家共用一個 JVM,只有一個實例,一但 JVM 掛了,所有跑在上面的應用程式都掛了,那使用者不是會氣死🤣

# 四、參考連結

  1. JVM vs DVM (opens new window)

  2. Dalvik 虛擬機和Sun JVM 在架構和執行方面有什麼本質區別? (opens new window)

  3. Android:Dalvik vs ART (opens new window)

  4. Closer Look At Android Runtime: DVM vs ART(推薦) (opens new window)

  5. JVM、Dalvik、ART 介紹 (opens new window)

  6. ProGuard (opens new window)

  7. Android ART vs Dalvik 差別 (opens new window)

  8. Android 與 OSGI 平台整合 (opens new window)

  9. ART vs Dalvik - Introducing the New Android x86 Runtime (opens new window)

  10. 深入理解Android虛擬機及編譯系統 (opens new window)

Last Updated: Tue Jul 06 2021 19:28:11 GMT+0800