Skip to content

Dalvik / GraalVM

过去的选择与未来的方向

这一节是 JVM 虚拟机家族的最后一站:一个是已经谢幕但影响深远的 Android 虚拟机,一个是正在定义未来的新一代运行时。

Dalvik:Android 的第一个虚拟机

出身

2003 年,Google 收购了 Android 公司。Android 团队需要一个能在手机内存和 CPU 约束下高效运行的 Java 虚拟机,但他们没有选择 HotSpot——一是因为许可证问题,二是因为 HotSpot 太重。

于是,他们从零设计了 Dalvik

关键区别:不是 JVM

Dalvik 是一个 Java 语言的实现,但不是一个 JVM 实现。它运行 Dex 字节码,而 Dex 不是 Class 文件格式。

Java 源码 (.java)
      ↓ javac
Class 文件 (.class)          Dex 文件 (.dex)
      ↓ dx 工具              ← Android SDK 工具链
JVM 执行               Dalvik / ART 执行

Android SDK 提供了一个 dx 工具,把所有的 Class 文件打包成一个 Dex 文件:

bash
# 编译 → 转换 → 运行
javac *.java          # 编译成 Class 文件
dx --dex --output=classes.dex *.class  # 转换成一个 Dex 文件
dalvik -cp classes.dex MainActivity   # Dalvik 执行 Dex

Dalvik 的设计优化

Dalvik 针对手机做了大量定制:

1. 寄存器式虚拟机

Dalvik 没有使用 JVM 的基于栈的设计,而是选择了基于寄存器的指令集。

java
// JVM 字节码(基于栈):
iload_1        // 局部变量槽 1 入栈
iload_2        // 局部变量槽 2 入栈
iadd           // 栈顶两数相加

// Dalvik 字节码(基于寄存器):
add-int v0, v1, v2   // v1 + v2 → v0

寄存器式指令通常比栈式指令执行效率更高(省去了入栈/出栈),但代价是跨平台适配更复杂。Dalvik 只需要支持 ARM 架构,不需要考虑跨平台。

2. 一个 Dex,多个 Class

JVM 中每个 Class 有一个独立的 Class 文件。Android 应用可能有几十个 Class,JVM 需要分别加载。

Dalvik 把所有 Class 打包成一个 Dex 文件。加载时只需要一次 I/O,节省了 SD 卡读取时间——这对当时的手机来说很重要。

3. 共享的 Dex

多个应用可以共享同一个 Dex(通过 sharedUserId 机制),减少手机的存储占用。

Dalvik 的生命周期

2008  Android 1.0  ──→ Dalvik 虚拟机首次登场
2010  Android 2.2  ──→ 引入 JIT 编译器(Dalvik JIT)
2014  Android 5.0  ──→ Dalvik 被 ART 取代
2017  Android 8.0  ──→ 完全移除 Dalvik

Dalvik 在 Android 存在了 9 年(2008~2017),服务了数十亿台设备。

ART:Dalvik 的继承者

什么是 ART

ART(Android Runtime)是 Android 5.0 引入的新运行时,取代了 Dalvik。

ART 的核心改变是:AOT(Ahead-Of-Time)编译

Dalvik:每次运行应用,都要把 Dex 字节码解释/即时编译
       → 启动慢,每次都编译,耗电

ART:安装应用时,就把 Dex 编译成本地机器码
    → 启动快,只编译一次,省电

ART 牺牲了安装速度(安装时需要编译),换来了运行时的流畅度和续航。

混合模式(JDK 7+)

2017 年 Android 8.0 引入了 ART 的混合编译模式

  • 安装时做部分 AOT 编译(加快安装速度)
  • 运行中用 JIT 补充编译热点代码
  • 最终把 JIT 编译结果缓存下来

这其实就是 HotSpot 分层编译思想的 Android 版本。

GraalVM:JVM 的未来形态

为什么需要 GraalVM

HotSpot 是一个用 C++ 写的虚拟机。C++ 的问题是:

  • 维护成本高:C++ 代码需要手动内存管理,容易出现 bug
  • 优化难度大:编译器层面的优化受限于 C++ 的表达能力
  • 多语言支持受限:不同语言想要在 HotSpot 上运行得好,需要自己实现 Truffle 框架

GraalVM 的答案是:用 Java 实现 JVM。TL;DR:如果 JVM 本身是用 Java 写的,那么优化它、扩展它、让它支持多语言,都会容易得多。

GraalVM 的架构

┌─────────────────────────────────────────────────────────────┐
│                      GraalVM                                │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐  │
│  │           GraalVM Languages (Truffle)                │  │
│  │  JavaScript  Python   Ruby   R    LLVM   WASM  ...  │  │
│  └─────────────────────────────────────────────────────┘  │
│  ┌─────────────────────────────────────────────────────┐  │
│  │                 GraalVM Core                        │  │
│  │   Graal Compiler (用 Java 实现的 JIT 编译器)        │  │
│  │   Substrate VM(用于 AOT 编译)                     │  │
│  └─────────────────────────────────────────────────────┘  │
│  ┌─────────────────────────────────────────────────────┐  │
│  │         JVMCI + HotSpot / SVM                       │  │
│  │   作为 HotSpot 的 JIT 插件 / 独立 VM 运行             │  │
│  └─────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

GraalVM 有两种使用模式:

模式一:HotSpot 的 JIT 插件

HotSpot JVM
      ↓ JVMCI 接口(Java-Level JVM Compiler Interface)
Graal JIT 编译器(用 Java 实现)

更高效的机器码

Graal 作为 HotSpot 的 JIT 插件,替代 C2 编译器。Graal 的优化能力比 C2 更强,特别是在特定场景下。

模式二:独立虚拟机(SubstrateVM)

GraalVM Native Image(SubstrateVM)
      ↓ AOT 编译
原生可执行文件(不含 JVM)

直接运行,不需要 JVM 安装

SubstrateVM 可以在构建时把 Java 应用和 GraalVM 一起编译成一个独立的原生可执行文件。没有 JVM 启动开销,启动速度从「秒级」变成「毫秒级」,内存占用也大幅降低。

bash
# 安装 GraalVM 后
gu install native-image

# 编译成原生可执行文件
native-image -jar myapp.jar myapp

# 运行
./myapp
# 启动时间从 2 秒变成 20 毫秒

GraalVM 的多语言支持:Truffle 框架

GraalVM 的另一个杀手锏是Truffle 框架——让任何语言都可以用 GraalVM 作为运行时。

传统方式:每种语言 → 自己实现解释器 → 性能差

Truffle 方式:语言 → Truffle AST 解释器 → Graal 自动优化 → 高性能

Truffle 让语言实现者只需要专注于语言本身的语义,不需要操心 JIT 优化。Graal 会自动把「解释执行」的 AST 编译成高效的机器码。

目前支持的语言:

语言说明
JavaScript速度比 V8 稍慢,但差距不大
PythonGraalPython,比 CPython 快
RubyTruffleRuby,比 CRuby 快
RFastR,适合数据处理
LLVM可以运行 C/C++ 编译出的 bitcode

GraalVM 的适用场景

GraalVM 不是银弹,它最适合以下场景:

场景原因
微服务 / Serverless原生镜像启动极快,适合无服务器架构
多语言混合一个进程里跑 Java + JS + Python,无互操作开销
JVM 性能优化用 Graal JIT 替代 C2,提升特定场景性能
工具类应用命令行工具需要秒级启动,原生镜像完美契合
数据库扩展PostgreSQL 的 PL/Java 用 GraalVM 加速

GraalVM 与 JDK 的关系

JDK 9+ 开始,Graal 已经被纳入 JDK 的模块系统:

bash
# JDK 17 内置 Graal
java -XX:+UseGraalJIT MyApp

# 或者用 GraalVM 发行版(JDK 17+)
# https://www.graalvm.org/

Oracle GraalVM 是最完整的发行版,包含了所有特性。OpenJDK 用户也可以通过 JAVA_HOME 切换使用 Graal JIT。

Dalvik 与 GraalVM 的对比

维度DalvikGraalVM
目标移动设备,低资源所有场景,高性能
实现方式从零实现用 Java 实现 JVM
字节码Dex(非标准)标准 Class 字节码
核心创新寄存器式,Dex 打包Truffle 框架,原生镜像
生命周期2008~2017,已停止2013~至今,活跃发展中
影响证明了非 JVM 的 Java 实现可行正在重新定义 JVM 的未来

本节小结

Dalvik 和 GraalVM 代表了两个极端:

  • Dalvik:从零实现,专为移动端优化,最终被 Google 放弃——它证明了 JVM 的替代品可以存在,但也证明了最终还是要拥抱标准
  • GraalVM:用 Java 实现 JVM,让 JVM 本身可以被优化和扩展——它代表了「元编程」的理念:如果你想让系统变得更好,就用这个系统本身的语言来实现它

到这里,「主流 JVM 虚拟机介绍」的全部章节就结束了。我们从 SUN Classic 一路讲到 GraalVM,JVM 生态的过去和未来都覆盖到了。

如果你想继续深入 JVM 之旅,接下来可以进入 JVM 运行时数据区(核心) 部分,这是 JVM 最核心、最实用的知识点。

基于 VitePress 构建