目录
  1. 1. 一、从 DEX 到 OAT:编译流水线
    1. 1.1. 1.1 整体流程
    2. 1.2. 1.2 dex2oat 的命令行
      1. 1.2.1. 1.2.1 参数详解
    3. 1.3. 1.3 dex2oat 的初始化与编译
      1. 1.3.1. 1.4 dex2oat 的进程模型
  2. 2. 二、OAT 文件结构
    1. 2.1. 2.1 OAT 文件头
    2. 2.2. 2.2 OatDexFile——每个 DEX 文件的元数据
    3. 2.3. 2.3 OatClass——每个类的编译状态
    4. 2.4. 2.4 OatQuickMethodHeader——每个方法的编译代码头
    5. 2.5. 2.5 Boot Image 与 .art 文件
  3. 3. 三、VDEX 文件格式
    1. 3.0.1. 3.1 Quickening 详解
  • 4. 四、Compilation Filter(编译过滤器)
    1. 4.0.1. 4.1 Profile-Guided Compilation 的工作原理
  • 5. 五、系统 OTA 升级时的 dexopt
    1. 5.1. 5.1 OAT 文件验证与失效判断
  • 6. 六、安装时 dexopt vs OTA dexopt 的区别
    1. 6.1. 6.1 Android 10+ 的 dexopt 延迟策略
    2. 6.2. 6.2 Cloud Profiles
  • 7. 七、核心面试题
    1. 7.1. 6.3 Android 12+ 的 ART 模块化
    2. 7.2. 6.4 dexlayout 与 dex 重排
    3. 7.3. 6.5 JIT 编译与 OAT 的关系
  • 8. 八、dex2oat 的编译流程深度剖析
    1. 8.1. 8.1 DEX 文件验证阶段
    2. 8.2. 8.2 Quickening 的具体优化内容
    3. 8.3. 8.3 AOT 编译的具体优化
    4. 8.4. 8.4 OAT 文件的 mmap 映射
    5. 8.5. 8.5 运行时方法查找的完整路径
  • 【深入内核篇】ODEX流程

    ODEX(Optimized DEX)和 VDEX(Verifiable DEX)是 Android 运行时性能的关键组件。从 APK 安装时触发的 dex2oat 编译,到 OAT 文件的结构、编译过滤器(compiler filter)的选择,再到系统 OTA 升级时的全局 dexopt——本文完整解析 Android DEX 优化流程。

    一、从 DEX 到 OAT:编译流水线

    1.1 整体流程

    APK 中的 classes.dex(Dalvik Executable 字节码)


    ┌─────────────────────────────────────┐
    │ dex2oat 编译器 │
    │ ┌─────────────────────────────────┐ │
    │ │ 1. 解析 DEX 文件 │ │
    │ │ 2. 验证字节码 │ │
    │ │ 3. 优化字节码(Quickening) │ │
    │ │ 4. 编译为机器码(AOT Compilation)│ │
    │ │ 5. 输出 OAT + VDEX 文件 │ │
    │ └─────────────────────────────────┘ │
    └─────────────────────────────────────┘


    ┌───────────────────────────────────────────────┐
    │ /data/dalvik-cache/ (或 /data/app/<pkg>/oat/)│
    │ ├── <pkg>.odex (OAT 文件) │
    │ └── <pkg>.vdex (VDEX 文件) │
    └───────────────────────────────────────────────┘

    AOSP 核心路径:

    • art/dex2oat/ — dex2oat 编译器源码
    • art/runtime/oat_file.hart/runtime/oat_file.cc — OAT 文件结构
    • art/runtime/oat_file_assistant.h — OAT 文件查找和验证
    • art/libdexfile/dex/ — DEX 文件解析库

    1.2 dex2oat 的命令行

    # 典型调用(由 installd 发起)
    dex2oat \
    --dex-file=/data/app/com.example.app-xxx/base.apk \
    --oat-file=/data/app/com.example.app-xxx/oat/arm64/base.odex \
    --compiler-filter=speed-profile \
    --instruction-set=arm64 \
    --android-root=/system \
    --app-image-file=/data/app/com.example.app-xxx/oat/arm64/base.art

    1.2.1 参数详解

    参数 含义
    --dex-file 输入 DEX 文件的路径(可以是 APK 文件也可以是独立的 .dex 文件)
    --oat-file 输出 OAT 文件的路径
    --compiler-filter 编译过滤器,控制编译粒度
    --instruction-set 目标 CPU 指令集(arm/arm64/x86/x86_64)
    --app-image-file 应用映像文件(用于存储 AOT 编译后的类对象)
    --classpath-dir Boot classpath 目录
    --profile-file Profile 文件路径(用于 speed-profile 过滤器)

    1.3 dex2oat 的初始化与编译

    // art/dex2oat/dex2oat.cc
    int main(int argc, char** argv) {
    // 1. 解析命令行参数
    auto dex2oat = std::make_unique<Dex2Oat>(&parser);

    // 2. 创建 Runtime(ART 运行时实例,用于编译)
    if (!CreateRuntime(dex2oat.get(), &args)) {
    return EXIT_FAILURE;
    }

    // 3. 加载 DEX 文件和 Class 验证
    dex2oat->LoadClassProfileDescriptors();

    // 4. 执行编译
    dex2oat->Compile();

    // 5. 输出 OAT + VDEX 文件
    dex2oat->WriteOatFiles();

    return EXIT_SUCCESS;
    }
    // art/dex2oat/dex2oat.cc
    void Dex2Oat::Compile() {
    // 遍历 class_defs,对每类进行编译
    TimingLogger::ScopedTiming t("dex2oat Compile", timings_);

    // 打开所有 DEX 文件
    OpenDexFiles();

    // 根据 compiler filter 决定编译粒度
    CompilerFilter::Filter filter = compiler_options_->GetCompilerFilter();

    // 对 DEX 中的类进行编译
    driver_->CompileAll(class_loader, dex_files_, timings_);
    }

    1.4 dex2oat 的进程模型

    dex2oat 不是以线程的方式在 system_server 中运行,而是由 installd 守护进程 fork 出独立进程。这样做的原因:

    1. 内存隔离:dex2oat 编译过程中会创建 ART Runtime,占用大量内存(首次编译可能需 100MB+),独立的进程在编译完成后可完全释放内存。
    2. 崩溃隔离:如果 dex2oat 因 OOM 或其他原因 crash,不会影响 system_server(AMS、PKMS 等)。
    3. 安全隔离:dex2oat 运行在 installd 的 selinux domain 下,不与 system_server 共享权限。

    二、OAT 文件结构

    2.1 OAT 文件头

    // art/runtime/oat.h
    class OatHeader {
    uint8_t magic_[4]; // "oat\n"
    uint8_t version_[4]; // OAT 版本号(如 0x00e3)
    uint32_t adler32_checksum_; // 文件完整性校验

    InstructionSet instruction_set_; // 目标指令集(arm64, x86_64 等)
    uint32_t instruction_set_features_bitmap_;

    uint32_t dex_file_count_; // 包含的 DEX 文件数
    uint32_t oat_dex_files_offset_; // OatDexFile 数组的偏移量
    uint32_t executable_offset_; // 可执行代码开始偏移
    uint32_t jni_dlsym_lookup_offset_;// JNI dlsym 查找表偏移
    uint32_t quick_generic_jni_trampoline_offset_;
    uint32_t quick_imt_conflict_trampoline_offset_;
    uint32_t quick_resolution_trampoline_offset_;
    uint32_t quick_to_interpreter_bridge_offset_;

    int32_t image_patch_delta_; // 如果是 boot.oat,此字段非零

    uint32_t image_file_location_oat_checksum_;
    uint32_t image_file_location_oat_data_begin_;

    uint32_t key_value_store_size_; // 键值对元数据存储大小
    // 后面跟着 key_value_store_ 数据(如 classpath 信息、安全补丁日期等)
    };

    magic 字段的作用"oat\n" 这个 4 字节的魔数用于快速识别文件类型。如果文件格式不兼容,版本号会变更,ART 在加载时检测并拒绝加载。

    key_value_store:一个字符串键值对列表,存储编译时的环境信息。如:

    • debuggable → “true”/“false”
    • classpath → “/system/framework/…”
    • compiler-filter → “speed-profile”

    2.2 OatDexFile——每个 DEX 文件的元数据

    // art/runtime/oat_file.h
    class OatDexFile {
    // 原始 DEX 文件的路径
    std::string dex_file_location_;

    // DEX 文件的 checksum
    uint32_t dex_file_location_checksum_;

    // 对应 DEX 文件在 OAT 中的偏移量
    uint32_t dex_file_offset_;

    // 此 DEX 文件中所有类的 OatClass 查找表
    // 格式:lookup_table[num_class_defs]
    // lookup_table[i] 指向第 i 个类的 OatClass 的状态/偏移量
    const uint32_t* lookup_table_data_;
    };

    OAT 文件中 DEX 文件偏移的格式是 0xPPPPNNNN,其中 PPPP 是 OAT 内部的偏移页,NNNN 标识具体位置。

    2.3 OatClass——每个类的编译状态

    // art/runtime/oat_file.h
    enum class OatClassType : uint8_t {
    kOatClassAllCompiled = 0, // 所有方法都编译为 native 代码
    kOatClassSomeCompiled = 1, // 部分方法编译
    kOatClassNoneCompiled = 2, // 无编译(解释执行)
    kOatClassMax = 3,
    };

    class OatClass {
    OatClassType type_;
    uint32_t methods_pointer_offset_; // 方法偏移表的偏移量
    // 如果 type_ == kOatClassSomeCompiled:
    // 后面跟着方法和代码的 bitmap + offset 表
    };

    对于 kOatClassSomeCompiled 的类,每个方法有一个 32-bit 的偏移量(相对于 OAT 文件起始位置),指向其在 OAT 中的编译代码。运行时使用 lookup_table[method_idx] 快速定位方法代码。

    2.4 OatQuickMethodHeader——每个方法的编译代码头

    // art/runtime/oat_quick_method_header.h
    class OatQuickMethodHeader {
    uint32_t vmap_table_offset_; // 虚拟映射表偏移
    uint32_t code_size_; // 编译代码的大小
    // 后面跟着实际的机器码
    };

    vmap_table 是 stack map(也称作 GC map),用于 GC 时解析栈帧——确定哪些寄存器/栈位置包含对象引用。

    2.5 Boot Image 与 .art 文件

    从 Android 5.0(ART 第一个正式版本)开始,系统预编译 boot classpath 中的类并存储在 /system/framework/arm64/boot.art 中。boot.art 是一个映像文件(image file),包含预初始化的类对象和它们的 AOT 代码:

    boot.art = 类对象的内存快照 + AOT 编译代码

    运行时,ART 直接 mmap boot.art 文件,避免了重新解析和编译 framework 类的大量工作。应用级别的 .art 文件(如 base.art)存储了应用自身的预加载类。

    三、VDEX 文件格式

    Android 8.0 引入了 VDEX 格式,包含:

    1. 未压缩的 DEX 文件:供运行时快速加载,无需从 APK zip 中解压
    2. Quickening 信息:预优化的字节码(如将虚方法调用的 method_idx 替换为 vtable index)
    3. 验证信息:DEX 字节码验证结果,运行时加载时可直接信任

    VDEX 的设计目标:将 DEX 验证和部分优化工作从 APK 首次启动时(Runtime)前置到安装时(dex2oat),从而显著减少应用的冷启动时间。

    // art/runtime/vdex_file.h
    class VdexFile {
    // VDEX 文件头
    struct VdexFileHeader {
    uint8_t magic_[4]; // "vdex"
    uint8_t version_[4]; // VDEX 版本号
    uint32_t number_of_sections_;
    // sections_: 包含 DEX 文件段、Quickening 信息段、验证依赖段
    };

    // 获取未压缩的 DEX 文件
    const uint8_t* GetNextDexFileData(const uint8_t* cursor) const;

    // 获取 Quickening 信息
    const uint8_t* GetQuickeningInfo() const;
    };

    3.1 Quickening 详解

    Quickening 是面向 DEX 字节码的优化,不需要编译成机器码。具体优化包括:

    1. 虚方法调用优化:将 invoke-virtual {v0, v1}, method@idx 中的 method_idx 替换为 vtable_index,运行时直接通过 vtable 跳转。
    2. 字段访问优化:将 iget/iput(实例字段访问)和 sget/sput(静态字段访问)的 field_idx 替换为字节偏移量。
    3. 内联简单方法:getter/setter 等单行方法直接在字节码层面内联,避免方法调用开销。

    Quickening 的优势:

    • 编译速度极快:不需要生成目标机器的指令,只是修改 DEX 字节码的某些字段
    • 文件体积增长小:比 AOT 编译的 .odex 小得多(约为原始 DEX 的 1.1-1.3x)
    • 与 Profile-Guided JIT 互补:Quickening 优化了所有方法的基础路径,JIT 再进一步优化热点方法

    四、Compilation Filter(编译过滤器)

    dex2oat 支持多种编译过滤器,控制 AOT 编译的粒度。由 compiler-filter 参数指定:

    Filter 行为 适用场景
    verify 仅验证 DEX 字节码,不编译任何方法 开发调试阶段
    quicken 优化字节码(Quickening),不编译为机器码 存储空间受限的设备
    speed-profile 基于 Profile 指导编译:热方法 AOT,冷方法解释/JIT 生产环境推荐(平衡性能与空间)
    speed 编译所有方法(full AOT) 对性能极致要求的场景
    everything 编译所有方法 + 运行时特殊路径(如 JNI stub) 极少使用(空间开销巨大)
    extract 从 APK 中提取并验证 DEX,不做任何编译 Android 11+ 新增,依赖 JIT
    verify-none 最低开销,跳过所有验证和编译 特殊场景(已预验证)
    // frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
    // 安装时指定编译过滤器
    void performDexopt(List<PackageSetting> pkgSettings, ...) {
    String compilerFilter;
    if (isSystemApp) {
    compilerFilter = "speed"; // 系统应用全 AOT
    } else if (isProfileGuidedCompile) {
    compilerFilter = "speed-profile"; // 第三方应用 profile guided
    } else {
    compilerFilter = "quicken"; // 仅 quickening
    }
    // 调用 installd 执行
    mInstaller.dexopt(pkgPath, ..., compilerFilter, ...);
    }
    // art/dex2oat/compiler_filter.h
    enum class Filter {
    kVerify, // 仅验证,无任何优化
    kQuicken, // Quickening 优化
    kSpeedProfile, // 基于 profile 的 AOT
    kSpeed, // 全 AOT
    kEverything, // 全编译
    };

    // 判断某个方法是否需要 AOT 编译
    bool CompilerFilter::IsAotCompilationEnabled(Filter filter) {
    return filter >= Filter::kSpeedProfile;
    }

    4.1 Profile-Guided Compilation 的工作原理

    speed-profile 过滤器依赖应用运行时的 Profile 数据。工作流程:

    1. 运行时:ART JIT 编译热点方法,同时记录这些方法的 ID 到 profile 文件
    2. 空闲时BackgroundDexOptService 触发 dex2oat --compiler-filter=speed-profile --profile-file=...
    3. dex2oat:只编译 profile 中记录的那些热方法(通常是全部方法的 10%-20%),其余方法保留为解释/JIT

    这称为 Profile-Guided Optimization (PGO) 在 Android 上的应用。场景评估:启动时常用路径约几千个方法被编译,90%+ 的非关键路径方法保有解释/JIT 覆盖,OAT 文件大小缩减到全 AOT 的约 20%。

    五、系统 OTA 升级时的 dexopt

    当系统 OTA 升级完成后,framework 的 boot classpath 发生变化,需要重新编译所有应用的 OAT 文件。这个操作由 ota-dexopt 触发:

    // frameworks/base/services/core/java/com/android/server/pm/BackgroundDexOptService.java
    public class BackgroundDexOptService extends Service {
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    // 在充电 + 空闲时,后台执行 dexopt
    // 常用于 OTA 升级后为所有应用重新编译
    mDexOptHandler.post(() -> {
    performBackgroundDexOpt(packageName);
    });
    return START_STICKY;
    }
    }

    系统升级后的 dexopt 路径:

    1. ota-dexoptbg-dexopt 脚本被触发
    2. installd 收到指令,遍历 /data/app/ 下所有应用
    3. 对每个应用调用 dex2oat,根据其当前的 compiler filter 重新编译
    4. 新生成的 .odex.vdex 文件覆盖旧的

    5.1 OAT 文件验证与失效判断

    ART 加载 OAT 文件时,通过 OatFileAssistant 进行多项检查:

    1. DEX checksum 匹配:当前 DEX 的 checksum 必须与 OAT 记录的一致
    2. Boot classpath 匹配:OAT 编译时的 boot.art checksum 必须与当前系统的匹配
    3. 版本号匹配:OAT 文件版本必须与 ART 支持的版本兼容

    如果 boot classpath 在 OTA 后发生变化(framework 方法被修改),所有依赖旧 boot classpath 的 AOT 编译代码都失效——其中内联了旧版本的 framework 方法。这也是为什么 OTA 需要重新 dexopt。

    六、安装时 dexopt vs OTA dexopt 的区别

    维度 安装时 dexopt OTA dexopt
    触发时机 APK 安装完成时(PackageInstallerSession.commit) 系统 OTA 升级后
    范围 仅新安装/更新的应用 所有已安装应用
    编译过滤器 新应用默认 speed-profile 继承原有 filter
    执行环境 可与前台安装并行(后台线程) 通常在充电且空闲时执行
    实现类 PackageManagerService.performDexopt BackgroundDexOptService / otapreopt
    优先级 中等 低(不影响前台使用)

    6.1 Android 10+ 的 dexopt 延迟策略

    Android 10 引入了 staged-dexopt——新安装应用不立即完全 dexopt,而是先做最小编译(verify 或 extract),待设备空闲/充电时再做完整 speed-profile 编译。这减少了安装等待时间。

    6.2 Cloud Profiles

    Android 10+ 引入了 Cloud Profiles(云配置文件),允许 Google Play 从云端下发应用的默认 profile。当用户首次安装应用时,即使没有本地 profile,也可以使用云端提供的 profile 指导 speed-profile 编译——这是基于”同型号设备上相似用户行为相似”的假设。

    七、核心面试题

    Q1:什么是 Quickening?它和 AOT 编译有什么区别?

    Quickening 是在 DEX 字节码层面进行的优化,而 AOT 是将 DEX 字节码编译为机器码。Quickening 包括:将虚方法调用的 method_idx 替换为 vtable index、优化字段访问为偏移量查找、内联简单的 getter/setter 字节码。它不需要生成机器码,因此编译速度极快,文件体积增长小。AOT 编译则完全生成本地机器指令,执行速度快但编译时间长、输出文件大。Android 采用分层编译:默认 Quickening + JIT(热方法被 JIT 编译为机器码),通过 profile 记录热点,在后台 dexopt 时对这些热方法做 AOT。

    Q2:VDEX 文件相比纯 OAT 有什么优势?为什么不直接全部放 OAT?

    VDEX 包含未压缩的 DEX 文件和 quickening 信息,与 OAT 分离有几个好处:(1) 当系统 OTA 升级(boot classpath 变化)时,OAT 需要重编译(因为 AOT 代码内联了 Framework 方法),但 VDEX 中的 DEX 和 quickening 信息仍然有效,可以继续用来解释执行。(2) 未压缩的 DEX 可以直接 mmap,无需从 APK zip 中解压,减少了冷启动时的 I/O。(3) VDEX 分离也方便独立更新 DEX 验证结果。

    Q3:dex2oat 在安装时被触发,如何确保不影响系统响应?

    dex2oat 不是在 SystemServer 进程中直接执行,而是通过 installd 守护进程 fork 出独立的 dex2oat 进程执行。installd 以低 I/O 优先级(ionice)和低 CPU 优先级(nice)运行 dex2oat,而且 PackageManagerService 使用 BackgroundDexOptService 管理编译队列,确保同一时间只运行有限数量的 dex2oat 进程(通常 1-2 个),避免资源争用影响前台应用。

    Q4:为什么 OTA 升级后需要对所有应用重新 dexopt?

    AOT 编译过程中,dex2oat 会将 framework 方法内联(inline)到应用的编译代码中。当 OTA 升级修改了 framework(boot classpath),所有内联了旧 framework 方法的 AOT 代码都处于不一致状态(调用的可能已经不存在的指令偏移或已修改的方法体)。OAT 文件通过记录编译时的 boot.art checksum 来检测 boot classpath 是否发生变化——如果发生变化,OAT 文件被标记为无效,需要重新编译。

    Q5:Cloud Profiles 是如何解决”冷启动无 Profile”问题的?

    传统 Profile-Guided Compilation 需要用户先使用应用,JIT 收集热点方法后生成 profile。但新用户或新安装时没有 profile,只能使用较低级别的过滤(如 quicken)。Cloud Profiles 通过聚合大量设备上的匿名 profile 数据,提取出”最常见的执行路径”,形成云端默认 profile。当用户安装应用时,Google Play 同时下发这个 profile,dex2oat 使用它进行 speed-profile 编译——实现了”首次安装即可获得 profile 指导的编译效果”。

    6.3 Android 12+ 的 ART 模块化

    Android 12 引入了 Mainline 模块化的 ART(通过 Google Play System Updates)。这意味着:

    • ART 运行时(包括 dex2oat)可以作为独立的 APEX 模块更新,而不用等待完整的系统 OTA
    • 更新周期从原来的一年缩短到几个月甚至几周
    • 新的 ODEX(.odex)文件格式与 ART 模块绑定的版本,确保一致性

    APEX 模块更新后,系统会触发一次针对所有已安装应用的重新 dexopt(类似于 OTA dexopt 但范围更小——仅 ART 模块变更时触发)。

    6.4 dexlayout 与 dex 重排

    Android 10 引入了 dexlayout 工具,在编译时优化 DEX 文件中方法和类的排列顺序:

    dexlayout 工作原理:
    1. 读取 profile 中的热方法列表
    2. 将热方法移动到 DEX 文件的前部(页面对齐)
    3. 将热方法调用的依赖数据(如方法 ID 表)也重排到前部
    4. 运行时 mmap 后,热方法集中在少数页面中,减少 TLB miss 和页面错误

    这称为 Profile-Guided Layout Optimization。重排后的 DEX 文件写入 VDEX 中。

    6.5 JIT 编译与 OAT 的关系

    ART JIT 编译在运行时产生临时的机器码,存储在进程的 JIT code cache 中(非文件)。JIT 的数据流:

    解释执行 → 热点检测(方法调用计数 + 循环回边计数)
    → JIT 编译(生成机器码到 JIT code cache)
    → 执行 JIT 编译的代码
    → 记录这些"热方法"到 profile
    → 空闲时 dex2oat 根据 profile 做 AOT 编译(写入 OAT)
    → 下次冷启动直接使用 OAT 中的 AOT 代码

    关键关系:JIT 是短期优化(运行时即时编译),OAT 是长期优化(持久化存储)。两者通过 profile 文件协同工作——JIT 标记哪些方法值得 AOT,dex2oat 在空闲时执行这些方法的 AOT 编译。

    AOSP 核心路径参考:

    • art/dex2oat/dex2oat.cc — dex2oat 编译器入口
    • art/dex2oat/compiler_filter.h — 编译过滤器定义
    • art/runtime/oat_file.h / art/runtime/oat_file.cc — OAT 文件定义
    • art/runtime/oat_header.h — OAT 文件头结构
    • art/runtime/vdex_file.h — VDEX 文件定义
    • art/runtime/oat_file_assistant.h — OAT 文件查找与验证
    • art/libdexfile/dex/ — DEX 文件解析库
    • frameworks/base/services/core/java/com/android/server/pm/BackgroundDexOptService.java
    • frameworks/native/cmds/installd/ — installd 守护进程

    八、dex2oat 的编译流程深度剖析

    8.1 DEX 文件验证阶段

    dex2oat 的验证阶段检查 DEX 字节码的正确性:

    验证内容:
    1. 结构验证:DEX header 的 magic、checksum、文件大小
    2. 类验证:每个类的继承关系、方法签名、字段类型正确性
    3. 字节码验证:每条指令的格式、操作数类型、控制流完整性
    4. 类型安全验证:方法调用参数类型匹配、字段访问类型匹配

    验证通过后生成 VDEX 文件中的 verification info。此后运行时加载时可以直接信任这些验证结果,跳过验证步骤——这是 VDEX 加速冷启动的核心。

    8.2 Quickening 的具体优化内容

    Quickening 是在 DEX 字节码层面进行的优化,不编译成机器码:

    Quickening 优化项:
    1. invoke-virtual → invoke-virtual-quick:将 method_idx 替换为 vtable_index
    2. invoke-interface → invoke-interface-quick:缓存接口方法表索引
    3. iget/iput → iget-quick/iput-quick:将 field_idx 替换为字节偏移量
    4. sget/sput → sget-volatile/sput-volatile:针对 volatile 字段的优化
    5. check-cast 消除:如果前一条指令已经验证了类型,消除重复的 check-cast
    6. instance-of 优化:缓存类标志位以便快速判断

    Quickening 的编译速度极快(通常几毫秒到几十毫秒),因为它不涉及任何指令集相关的代码生成——只做索引替换。

    8.3 AOT 编译的具体优化

    AOT 编译产生目标机器的本地代码时,进行的优化包括:

    AOT 优化项(与 LLVM/Clang 编译优化类似):
    1. 方法内联(Method Inlining):
    - 内联短小的方法体(getter/setter/callback)
    - 内联 framework 方法(需要重新编译 OAT 当 framework 变更时)
    2. 循环优化(Loop Optimization):
    - 循环不变量外提(LICM)
    - 循环展开(Loop Unrolling)
    - 归纳变量优化(Induction Variable Optimization)
    3. 死代码消除(Dead Code Elimination)
    4. 常量折叠(Constant Folding)
    5. 逃逸分析(Escape Analysis)
    6. 寄存器分配(Register Allocation):
    - 针对 ARM64(31 个通用寄存器)的寄存器分配

    8.4 OAT 文件的 mmap 映射

    运行时加载 OAT 文件的关键是 mmap:

    // art/runtime/oat_file.cc
    bool OatFile::Load(const std::string& oat_filename, ...) {
    // 1. 打开 OAT 文件
    int fd = open(oat_filename.c_str(), O_RDONLY);

    // 2. mmap 整个文件到进程地址空间
    void* mapped = mmap(nullptr, file_size, PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);

    // 3. 解析 OatHeader(从 mmap 的内存中直接读取,无需 read 系统调用)
    OatHeader* header = reinterpret_cast<OatHeader*>(mapped);

    // 4. 查找方法代码:
    // OatDexFile → OatClass → OatMethod → OatQuickMethodHeader → native code
    const void* method_code = GetOatMethod(method_index);
    // 直接跳转执行 method_code(指向 mmap 映射区域中的 native 代码)
    }

    mmap 的核心优势

    • 零拷贝访问:文件内容直接映射到进程地址空间,无需 read() 系统调用
    • 按需加载:操作系统按 page fault 加载必要的页(惰性加载)
    • 页面共享:同一个 OAT 文件被多个进程 mmap 时,物理内存页共享(节省 RAM)
    • 可执行权限:通过 PROT_EXEC 标记,mmap 的页面可直接执行 native 代码

    8.5 运行时方法查找的完整路径

    当 ART 需要执行一个 Java 方法时:

    1. 获取 method_index(在 DEX 中的方法编号)
    2. 找到 OatDexFile(对应此 DEX 文件)
    3. 通过 lookup_table[method_index] 获取 OatClass 偏移
    4. 检查 OatClass 类型:
    a. kOatClassAllCompiled:所有方法有偏移表
    b. kOatClassSomeCompiled:部分方法有偏移表(带 bitmap 过滤)
    c. kOatClassNoneCompiled:走解释器或 JIT
    5. 如果有 AOT 代码:跳转到 mmap 区域中的 native 代码地址
    6. 如果无 AOT 代码:
    a. 检查 JIT code cache(是否有 JIT 编译的版本)
    b. 如果无 JIT:进入解释器(interpreter)

    AOSP 核心路径参考续:

    • art/runtime/oat_file.cc — OAT 文件加载与 mmap
    • art/runtime/interpreter/interpreter.cc — ART 解释器
    • art/compiler/optimizing/ — AOT 编译优化器
    打赏
    • 微信
    • 支付宝

    评论