Dalvik仮想マシンはAndroidオペレーティングシステムの主要コンポーネントであり、バージョン4.4 KitKatまでのアプリケーション実行を担当していました。Dan Bornsteinによって開発されたこのレジスタベースのVMは、標準的なJVMの概念に代わり、RAMが限られたモバイルデバイスでのアプリケーション起動を最適化することを可能にしました。Google、2024によると、DalvikはJITコンパイルを通じてアプリケーションの互換性を確保し、実行中にDEXバイトコードを直接マシン命令に変換していました。
重要なポイント
Dalvikはレジスタベースのアーキテクチャを持つ仮想マシンで、Androidプラットフォーム専用に作成されました。開発は2005年にDan Bornsteinの会社によって開始され、2007年にGoogleがプロジェクトを買収しました。Dalvikの最初の商用バージョンは2008年のAndroid 1.0リリースとともに登場しました。
標準的なJava仮想マシン(JVM)とは異なり、DalvikはJavaバイトコードを実行しません。Javaコンパイラはソースコードをclassファイルに変換し、dxユーティリティがそれらをDalvik Executable(DEX)形式に変換します。この形式はclassファイルよりもコンパクトで、class形式で10MBのアプリケーションはDEXでは約6–7MBになります。
Dan BornsteinはDalvikをリソースが限られたオペレーティングシステム向けのプロジェクトとして書きました。名前はアイスランドの村ダルヴィークに由来します。Googleはライセンス制限とARMアーキテクチャを採用するモバイルプロセッサ向けの深い最適化の必要性から、JVMではなくDalvikを選択しました。このシステムは急速に普及し、2012年までに5億台以上のAndroidデバイスがDalvikで動作していました。
各Androidアプリケーションは、独自のDalvik VMインスタンスを持つ別々のプロセスで実行されます。これにより、オペレーティングシステムレベルでのデータの分離と悪意のあるコードからの保護が保証されます。このアプローチは、仮想化の利点とLinuxサンドボックスを組み合わせたもので、あるアプリケーションのマルウェアが隣接するプロセスに影響を与えることはありません。
Dalvikのレジスタベースのアーキテクチャは、JVMのスタックベースのアーキテクチャとは根本的に異なります。Dalvikはスタックトップでの操作の代わりに、VM内部の仮想セルであるレジスタを操作します。各命令にはオペランドレジスタのアドレスが含まれており、1操作あたりの命令数が削減されます。
JVMスタックマシンはpush、pop、addなどの命令を使用します—— 2つの数値を加算するには3つの命令が必要です。Dalvikは3つのレジスタを持つ1つのadd-int命令で同じタスクを解決します。Android Open Source Projectによると、レジスタベースのDEXアーキテクチャは、スタックベースのclass形式と比較してバイトコードサイズを平均30%削減します。
DEXファイル(Dalvik Executable)には、アプリケーションのすべてのクラスの圧縮表現が含まれています。ファイルヘッダーにはチェックサム、セクションサイズ、オフセットが含まれます。主要なセクションは、文字列、型、メソッドプロトタイプ、フィールド、およびバイトコード自体のプールです。1つのDEXファイルには最大65,536のメソッドを格納できます(Android 5.0でマルチデックスが導入され、制限は解除されました)。
Android SDK Build Toolsに含まれるdxユーティリティは、classファイルをDEXに変換するために使用されます。コマンドの例:dx --dex --output=classes.dex myapp.jar。最新のプロジェクトでは、最適化が改善されJava 8+機能をサポートするdxの後継であるD8を使用しています。
# dxを使用したJARからDEXへの変換
dx --dex --output=classes.dex myapp.jar
# D8による最新バージョン
d8 --lib android.jar --output dex/ myapp.jar
ZygoteプロセスはDalvikアーキテクチャの重要な要素です。システム起動時、ZygoteはすべてのAndroid SDKクラスをロードし、共有ライブラリを開き、プリロードされたリソースのプールを作成します。ユーザーがアプリケーションを開くと、システムはZygoteプロセスをフォークし、既に初期化されたフレームワークを持つ新しいDalvik VMインスタンスを作成します。これにより、アプリケーションの起動時間が約2–3秒から300–500ミリ秒に短縮されます。
JIT(Just-In-Time)は、アプリケーション実行中にバイトコードを直接マシン命令にコンパイルする技術です。Dalvikでは、JITコンパイラが実行中のDEXコードを分析し、頻繁に使用される(ホットな)メソッドを特定して、それらをCPU用のネイティブコードにコンパイルします。
初期のAndroidバージョンで完全なAhead-Of-Time(AOT)コンパイルではなくJITを選択したのは意図的でした。モバイルデバイスはフラッシュストレージが限られており(4–16GB)、すべてのアプリケーションを事前コンパイルするとかなりの容量を占有していたでしょう。さらに、初期のデバイスではROMメモリがRAMよりも遅く、事前にコンパイルされたコードを読み取るとパフォーマンスが低下する可能性がありました。
アプリケーションが起動すると、DalvikはDEXバイトコードの解釈を開始します。特別なプロファイラがどのメソッドが最も頻繁に呼び出されるかを追跡します。しきい値(通常は約200回の呼び出し)を超えると、JITコンパイラがメソッドをマシンコードに変換し、RAMにキャッシュします。後続の呼び出しは、再コンパイルなしで既にコンパイルされたバージョンを使用します。
// JITがコンパイルするホットメソッドの例
public class Calculator {
public int sumArray(int[] arr) {
int total = 0;
for (int i = 0; i < arr.length; i++) {
total += arr[i];
}
return total;
}
}
Google I/O 2013によると、Android 2.2 FroyoでのJITの導入により、純粋な解釈と比較してアプリケーションの実行が平均2–5倍高速化されました。ただし、JITは最初の起動時にレイテンシを追加します。アプリケーションはウォームアップとホットメソッドのコンパイルに3〜10秒を必要とします。ウォームアップ後、パフォーマンスはネイティブコードに近いレベルで安定します。
Dalvikはいくつかの基本的な点でJVMと異なります。第一にアーキテクチャ:JVMはスタックベース、Dalvikはレジスタベースです。第二にバイトコード形式:JVMはclassファイルを使用し、DalvikはDEXを使用します。第三にメモリ管理:Dalvikはモバイルデバイスの限られたRAM向けに最適化されています。
どちらのアプローチにも強みがあります。スタックベースのJVMは命令の保存に必要なスペースが少なく—— 各命令はオペランドが暗黙的にスタックから取得されるため短くなります。レジスタベースのDalvikは1操作あたりの実行命令数が少なく、CPU時間を節約し、消費電力を削減します。バッテリー駆動のモバイルデバイスでは、これが重要です。
| パラメータ | Dalvik | JVM |
|---|---|---|
| アーキテクチャ | レジスタベース | スタックベース |
| バイトコード | DEX | class |
| コンパイル | JIT(Android 2.2+) | JIT / AOT |
| 最適化 | 低消費電力 | 高い互換性 |
| 分離 | Linuxプロセス経由 | ClassLoader経由 |
JVMではなくDalvikを選択したのは、ライセンスによるものでもありました。OracleはJava SEとJVMの権利を所有しており、Googleはライセンス料を回避しようとしていました。代替のバイトコード形式を持つ独自のVMを作成することで、AndroidはOracleから独立して開発できるようになりました。この紛争は、Oracle対Google(2010–2021)の長期にわたる法廷闘争に発展し、Googleの勝訴で終わりました。
DEX(Dalvik Executable)はAndroidアプリケーションのコンパイル済みコードを含むバイナリ形式です。各DEXファイルはヘッダーで始まり、その後にセクションが続きます:文字列定数(string_ids)、型(type_ids)、メソッドプロトタイプ(proto_ids)、フィールド(field_ids)、メソッド(method_ids)、クラス定義(class_defs)、およびデータ領域です。
dxユーティリティはJavaクラスファイルを1つ以上のDEXファイルに変換します。アルゴリズムには定数の重複排除が含まれており、同一の文字列や型は一度だけ保存され、インデックスで参照されます。これにより最終的なサイズが大幅に削減されます。最新のプロジェクトでは、dxはD8(Android Studio 3.1で導入)に置き換えられており、2–3倍高速でJava 8のデシュガリングをサポートしています。
// dexdumpによる逆コンパイルされたDEXバイトコードの例
// ソースコード:return a + b;
@Ldalvik/annotation/Code;
registers: 3
add-int v0, v1, v2
return v0
65,536メソッドというDEX形式の制限(16ビットインデックスの制限)は、大規模なアプリケーションにとって深刻な問題になりました。解決策はAndroid 5.0で登場しました。マルチデックスサポートにより、アプリケーションは複数のDEXファイルを含めることができます。メインのclasses.dexにはエントリポイントが含まれ、追加のclasses2.dex、classes3.dexなどには残りのコードが含まれます。マルチデックスの設定は、build.gradleでmultiDexEnabled trueという行で有効にします。
Dalvikのガベージコレクションは、マークアンドスイープを備えた世代別コレクタとして実装されています。メモリは2つの主要な領域に分かれています:オブジェクト用のヒープと、プリミティブと参照用のスタックです。ヒープがいっぱいになると、Dalvikはすべてのスレッドを一時停止し(STW — Stop-The-World)、到達可能なオブジェクトをマークし、到達不能なオブジェクトを解放します。
Android 2.2以前では、Dalvikは最大100–200ミリ秒の一時停止時間を持つシングルスレッドコレクタを使用していました。Android 2.3 Gingerbreadでは、一般的な一時停止を5–10ミリ秒に短縮するコンカレントコレクタが導入されました。そしてAndroid 4.0 Ice Cream Sandwichでは、増分クリーンアップを備えたコレクタ——Concurrent Mark and Sweep(CMS)——が追加されました。
Dalvikアプリケーションの典型的な問題は、Activityへの静的参照によるメモリリークです。静的フィールドがContextやViewへの参照を保持している場合、ガベージコレクタは画面が閉じられた後でもActivityを解放できません。Eclipse MATやLeakCanaryのようなツールは、このようなリークの検出に役立ちます。ヒープダンプを分析し、オブジェクトを保持する参照チェーンを表示します。
// 静的参照によるメモリリークの例
public class Utils {
private static Context context;
public static void init(Context ctx) {
context = ctx; // finish()後にActivityを保持
}
}
成功にもかかわらず、Dalvikにはいくつかの欠点がありました。JITコンパイルにはウォームアップ時間が必要で、アプリケーション動作の最初の数秒間は遅くなりました。さらに、JITはコンパイル中にCPU電力を消費し、バッテリー寿命を短縮しました。モバイルデバイスの性能が向上し、内蔵ストレージが増えるにつれて、JITの必要性は減少しました。
Android 4.4 KitKatで、GoogleはDalvikの実験的な後継としてART(Android Runtime)を導入しました。Android 5.0 Lollipop以降、ARTが唯一のランタイム環境になりました。主な違いはAOTコンパイルです。実行中にコンパイルする代わりに、すべてのアプリケーションはインストール時にマシンコードにコンパイルされます。これによりウォームアップの遅延がなくなり、エネルギー効率が向上しました。
DalvikからARTへの移行は開発者にとって透過的でした。両方のランタイムは同じDEXバイトコードを実行します。Dalvik用にコンパイルされたアプリケーションは、再コンパイルなしでART上で動作します—— system_serverがインストール時にそれらをネイティブコードにコンパイルします。例外は、リフレクションを使用してDalvik VMの内部メンバーにアクセスするコードです。内部アーキテクチャの変更により、そのようなコードはARTで壊れる可能性があります。
よくある質問
Dalvikは、電話機でAndroidアプリケーションを実行する仲介プログラムです。アプリケーションコードを受け取り、ユーザーが作業している間に直接、プロセッサが理解できるコマンドに変換します。
DalvikはレジスタベースのアーキテクチャとDEX形式を使用し、JVMはスタックベースのアーキテクチャとclass形式を使用します。Dalvikはメモリと処理能力が限られたモバイルデバイス向けに最適化されていますが、JVMはデスクトップコンピュータとサーバー向けに設計されています。
ARTは事前AOTコンパイルにより高いパフォーマンスを提供します—— アプリケーションは起動のたびではなく、インストール時に一度だけコンパイルされます。これにより、DalvikのJITアプローチと比較して動作が高速化され、バッテリーを節約できます。
はい。ARTはDalvik DEXバイトコードと完全な後方互換性があります。インストール時に、ARTは古いDEXファイルをネイティブコードにコンパイルします。例外は、リフレクションを使用してDalvikの内部メカニズムにアクセスするアプリケーションです。
DEX(Dalvik Executable)は、Androidアプリケーションの圧縮バイトコードを含む実行可能ファイル形式です。1つのAPKに65,536を超えるメソッドがある場合、複数のDEXファイル(マルチデックス)を含めることができます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。