第 18 章:ART 运行时

Android 运行时(ART)是每一个 Android 应用核心的托管执行环境。它加载、校验并执行 DEX 字节码 —— 即 Java 与 Kotlin 源码编译后的输出产物。ART 在 Android 5.0(Lollipop)版本取代 Dalvik,之后经历了巨大的演进:从简单的解释器 + AOT 模型,演变为一套具备并发垃圾回收、设备端配置文件引导优化、通过 Mainline 项目模块化交付的多层级高级编译引擎。

本章完整讲解 Android 托管代码的整个生命周期:从 DEX 文件格式,到 AOT 提前编译与 JIT 即时编译,再到内存管理以及原生代码互操作,全部基于 AOSP 源码展开。

18.1 ART 架构 

18.1.1 目录结构 

art / 下的 ART 工程主要划分为如下子目录:

目录 用途
runtime/ 核心运行时:类链接器、GC、JIT、JNI、线程
compiler/ 优化编译器后端(13 个子目录)
dex2oat/ AOT 编译器驱动(7 个子目录)
libdexfile/ DEX 文件解析与校验库
libartbase/ ART 各组件共用的基础工具库
libartpalette/ 平台抽象层
libartservice/ ART 系统服务(artd)
libarttools/ 命令行工具工具集
libprofile/ 配置文件数据读写
libnativebridge/ 用于指令集转换的 NativeBridge 接口
libnativeloader/ 按应用进行原生库加载
libelffile/ 用于读取 OAT 文件的 ELF 文件解析
odrefresh/ OTA 升级后设备端刷新工具
openjdkjvm/ JVM 接口实现
openjdkjvmti/ 用于调试的 JVMTI 代理实现
artd/ ART 守护进程(dexopt 服务)
perfetto_hprof/ Perfetto 堆分析器集成模块
dalvikvm/ dalvikvm 命令行启动器
dexdump/ DEX 文件转储工具
oatdump/ OAT 文件转储工具
profman/ 配置文件管理工具
sigchainlib/ 信号链库
disassembler/ 机器码反汇编器
tools/ 各类辅助脚本与工具
test/ 测试基础设施与测试用例
benchmark/ 性能基准测试
build/ 编译系统集成

18.1.2 运行时单例 

Runtime类是核心单例对象,持有全部主要子系统。该类声明于art/runtime/runtime.h,实现代码约 3000 行,位于art/runtime/runtime.cc

// art/runtime/runtime.h, line 130
class Runtime {
 public:
  static bool Create(RuntimeArgumentMap&& runtime_options)
      SHARED_TRYLOCK_FUNCTION(true, Locks::mutator_lock_);

  bool Start() UNLOCK_FUNCTION(Locks::mutator_lock_);

  static Runtime* Current() {
    return instance_;
  }
  ...
};

Runtime 持有的关键子系统:

  • ClassLinker —— 类加载、链接、校验、符号解析
  • Heap (gc::Heap) —— 托管堆、垃圾回收器
  • Jit (jit::Jit) —— JIT 编译器前端、线程池、性能采样
  • JitCodeCache —— 编译后代码存储,代码垃圾回收
  • JavaVMExt —— JNI JavaVM 实现、原生库管理
  • InternTable —— 字符串驻留表
  • ThreadList —— 线程管理、线程挂起、检查点
  • OatFileManager —— OAT 文件查找与加载
  • MonitorList / MonitorPool —— 对象监视器管理
  • SignalCatcher —— 处理 ANR 转储的 SIGQUIT 信号处理器
  • Instrumentation —— 方法进入 / 退出钩子
  • Trace —— 方法跟踪
  • RuntimeCallbacks —— 插件与代理的扩展点

18.1.3 Runtime 启动流程 

Zygote 进程启动,或者直接调用 dalvikvm 时,运行时会执行如下初始化序列:

  1. 解析参数Runtime::ParseOptions()处理命令行参数,例如堆大小、编译器过滤规则、GC 配置。
  2. 创建运行时实例Runtime::Create()分配并初始化单例 Runtime 对象。
  3. 初始化堆gc::Heap构造函数创建内存空间(区域堆空间、大对象空间、非移动空间),选定垃圾回收器。
  4. 加载 boot 镜像ClassLinker::InitFromBootImage()将预编译的 boot 镜像(.art文件)映射到内存,加载java.lang.Objectjava.lang.String等基础类。
  5. 初始化类链接器 — 注册 boot 类路径 DEX 文件,创建 DexCache 对象,初始化字符串驻留表。
  6. 创建 JIT — 如果开启 JIT 编译(应用进程默认开启),Jit::Create()加载编译器共享库,创建 JIT 线程池。
  7. 启动运行时Runtime::Start()切换至完整运行状态:启动信号捕获线程、堆任务处理器、终结器守护线程。
// art/runtime/runtime.cc, lines 96-97 (include chain)
#include "jit/jit.h"
#include "jit/jit_code_cache.h"

18.1.4 Zygote 与进程模型 

ART 高度依赖 Zygote 进程模型实现高效进程创建:

  1. Zygote 启动:Zygote 进程启动 ART 运行时,加载 boot 镜像,预初始化常用类。
  2. fork:需要新建应用进程时,Zygote 执行fork()。子进程会继承:
  3. 完整 boot 镜像内存映射(共享只读)
  4. 已加载类及其元数据
  5. 字符串驻留表
  6. Zygote JIT 模式下的 JIT 代码缓存
  7. 子进程特化:子进程做自身初始化工作:
  8. 加载应用的 DEX、OAT 文件
  9. 创建应用专属类加载器
  10. 根据应用内存等级调整 GC 配置
  11. 启动 JIT 线程池

该模型下 ART 运行时的初始化开销仅在 Zygote 中执行一次,所有应用通过写时复制机制共享 boot 镜像内存页。

Zygote 相关核心运行时方法:

// art/runtime/runtime.h (selected methods)
bool IsZygote() const { return is_zygote_; }
bool IsPrimaryZygote() const { return is_primary_zygote_; }
bool IsSystemServer() const { return is_system_server_; }

void SetAsSystemServer() {
  is_system_server_ = true;
  is_zygote_ = false;
  is_primary_zygote_ = false;
}

18.1.5 App 镜像

除 boot 镜像之外,ART 可以为每个应用生成 App 镜像,保存应用预初始化完成的类状态。使用speed‑profile过滤规则时,dex2oat 会生成 App 镜像,作为.art文件与.oat文件放在一起。

App 镜像包含:

  • 应用 DEX 文件中经过预初始化的类
  • 应用使用的已解析字符串
  • 应用 DEX 文件对应的 DexCache 条目

应用启动时,运行时将 App 镜像映射进堆内存,省去重复加载、初始化类的开销,大幅降低类数量较多应用的冷启动时延。

18.1.6 编译流水线总览 

ART 采用多层级编译策略:

流水线支持多种compiler filters(编译器过滤规则),控制 dex2oat 的工作内容(见 18.3.3 小节)。

18.1.7 执行模式 

ART 对同一个方法提供 4 种执行模式:

  1. 解释器 (Nterp):手写汇编实现的解释器,逐条执行 DEX 字节码。Nterp 针对不同架构(ARM64、x86‑64、RISC‑V)做优化,同时采集性能采样数据。
  2. 基线 JIT:轻量非优化 JIT 编译,对达到热度阈值的方法快速生成原生代码。
  3. 优化 JIT:对热点方法做完整优化编译。
  4. AOT(dex2oat):提前编译生成存放在 OAT 文件中的原生代码。

art/runtime/compilation_kind.h中定义CompilationKind枚举:

// art/runtime/compilation_kind.h, lines 27-32
enum class CompilationKind {
  kOsr = 0,        // On‑Stack Replacement 栈上替换
  kFast = 1,       // Fast (pattern‑matched) compilation 快速模式匹配编译
  kBaseline = 2,   // Non‑optimizing baseline 非优化基线编译
  kOptimized = 3,  // Full optimization 完整优化编译
};

18.1.8 Nterp:高速解释器 

Nterp(Next‑generation TeRP,读作 en‑terp)是 ART 的主力解释器。不同于传统基于 C++ switch 的解释器,Nterp 为每一种支持的架构手写汇编实现。消除 C++ 函数调用开销,实现寄存器到寄存器的 DEX 字节码执行。

源码路径:art/runtime/interpreter/mterp/(各架构汇编文件)。

Nterp 核心特性:

  • 直接线程调度:每条 DEX 指令处理器直接跳转至下一条指令的处理函数,消除 switch 分发开销。
  • 寄存器映射:DEX 虚拟寄存器映射到栈上一块连续内存,高频访问寄存器直接使用机器寄存器。
  • 内联缓存:Nterp 在虚调用位置记录接收者类型,供 JIT 内联器使用。
  • 热度计数:每一次方法调用、回跳分支都会递减热度计数器;计数器归零时通知 JIT。
  • GC 协同:在回跳分支、方法入口处设置安全点检查,支持 GC 挂起线程。

Nterp 很好平衡启动时延(无编译耗时)与执行速度,比朴素 C++ 解释器快 2‑5 倍。

18.1.9 线程模型 

ART 通过Thread类(art/runtime/thread.h)和ThreadList类(art/runtime/thread_list.h)管理 Java 线程。

每个 Thread 对象包含:

  • 线程本地状态 — 当前状态(Runnable、Native、Suspended、WaitingForGc 等)
  • 托管栈 — 用于栈回溯的托管帧链表
  • JNI 局部引用 — 线程本地 JNI 引用表
  • TLAB — 线程本地分配缓冲区,用于快速对象分配
  • Mark 栈 — 并发 GC 使用的线程本地标记栈
  • Exception — 当前待抛出异常
  • Class table — 事务模式下快速查找类

线程状态控制 GC 协同:

只有处于kRunnable状态的线程可以访问托管堆。GC 执行部分操作前,要求所有 mutator 线程到达安全点(退出 kRunnable 状态)。

18.1.10 锁基础设施 

ART 使用一套定义明确的锁层级避免死锁。依靠注解(REQUIRESREQUIRES_SHAREDGUARDED_BY)与编译期检查强制锁顺序。

按获取顺序排列的核心锁:

  1. mutator_lock_ — 核心读写锁,隔离 mutator 线程(读)与 GC(独占写)。绝大多数运行时操作以共享模式持有该锁。
  2. heap_bitmap_lock_ — GC 标记、清扫阶段保护 GC 位图。
  3. classlinker_classes_lock_ — 类加载过程保护类表。
  4. dex_lock_ — 保护已注册 DEX 文件列表。
  5. jni_libraries_lock_ — 保护原生库表。
  6. thread_list_lock_ — 保护全局线程列表。

18.1.11 信号链库 

信号链库(art/sigchainlib/)管理信号处理,让 ART 信号处理器与应用、NDK 信号处理器共存。信号到来时:

  1. ART 处理器优先处理。
  2. 如果 ART 不处理(非托管故障),则传递给下一个处理器。
  3. 全部处理器都不处理,则执行系统默认行为。

该机制对以下场景至关重要:

  • 空指针异常检测(SIGSEGV)
  • 栈溢出检测(SIGSEGV/SIGBUS)
  • GC 隐式挂起(读屏障页面触发 SIGSEGV)
  • ANR 信息转储(SIGQUIT)

18.1.12 异常处理 

ART 异常处理遵循 Java 异常模型:

  1. 抛出:执行 throw 指令时,运行时在当前线程设置待处理异常。
  2. 栈展开:栈遍历器向上遍历调用栈,寻找匹配的 catch 处理器。
  3. 捕获:找到匹配处理器后,在对应 PC 位置恢复执行;通过move‑exception指令将捕获异常存入专用寄存器。
  4. 未捕获:找不到处理器时,调用线程的UncaughtExceptionHandler

编译代码依靠栈映射表确定每个 PC 位置有效的异常处理器;解释代码读取 CodeItem 中的 try‑item 表。

CommonThrows模块(art/runtime/common_throws.h)提供常用异常的工厂函数:

// art/runtime/common_throws.h (selected)
void ThrowNullPointerExceptionForFieldAccess(ArtField*, bool is_read);
void ThrowNullPointerExceptionForMethodAccess(uint32_t method_idx, InvokeType);
void ThrowArrayIndexOutOfBoundsException(int index, int length);
void ThrowClassCastException(ObjPtr<mirror::Class> actual, ObjPtr<mirror::Class> expected);
void ThrowArithmeticExceptionDivideByZero();
void ThrowStackOverflowError(Thread* self);
void ThrowNoSuchMethodError(InvokeType, mirror::Class*, std::string_view, Signature);
void ThrowNoSuchFieldError(std::string_view scope, mirror::Class*, std::string_view);

18.1.13 Monitor 与同步机制 

Java 的synchronized关键字、Object.wait() / Object.notify()由 monitor 系统实现(art/runtime/monitor.h)。

轻量锁 vs 重量锁 

ART 使用双层锁机制:

  • 轻量锁:无竞争场景,锁信息直接存放在对象的锁字(每个对象第一个字)。轻量锁获取仅一次 CAS(比较并交换)操作。

锁字布局(32bit) [31:30] 状态 (00 = 未锁定,01 = 轻量锁,10 = 重量锁,11 = 哈希) [29:16] 锁计数(轻量锁递归深度) [15:0] 线程 ID(轻量锁)

  • 重量锁:发生竞争(其他线程尝试获取被别的线程持有的轻量锁)时,轻量锁膨胀为重量锁。重量锁使用 Monitor 对象,附带条件变量,支持wait()/notify()

CAS 成功 → 递归锁计数 + 1 → 释放; 检测到竞争 → 锁膨胀; 释放锁:条件允许则降级; Object.wait → 进入等待;notify/notifyAll 唤醒。

MonitorPool

MonitorPoolart/runtime/monitor_pool.h)预分配 Monitor 对象,避免锁膨胀时执行对象分配(防止在不合时宜的时机触发 GC)。

18.1.14 内置方法 (Intrinsics)

ART 识别部分 Java 方法为 intrinsic 内置方法,提供绕过常规调用流程的优化实现。内置方法在各架构代码生成器中实现,对以下场景带来显著性能提升:

  • 数学运算 — Math.abs()Math.min()Math.max()Math.sqrt()直接使用硬件指令。
  • 字符串操作 — String.charAt()String.compareTo()String.equals()使用优化汇编。
  • 对象操作 — Object.getClass()直接读取类字段。
  • 系统操作 — System.arraycopy()使用优化内存拷贝。
  • Unsafe 操作 — Unsafe.compareAndSwapInt()等使用硬件 CAS 指令。
  • 线程操作 — Thread.currentThread()直接读取线程本地存储。

是否为内置方法由ArtMethod::access_flags_中的kAccIntrinsic标记位控制;附加 bit 位区分具体是哪一个内置方法。

// art/runtime/art_method.h, lines 256-258
bool IsIntrinsic() const {
  return IsIntrinsic(GetAccessFlags());
}

18.1.15 内存对象布局 

ART 为性能设计了专用对象内存表示。

对象布局 

每个 Java 对象以两个字的对象头开头:

  1. 锁字 (32bit):monitor 状态、哈希码、GC 转发指针。
  2. 类指针 (32bit,压缩引用):指向mirror::Class对象。

实例字段紧跟对象头,排布顺序尽可能减少内存对齐填充:

  1. 引用类型字段优先(便于 GC 扫描)
  2. 64 位字段、32 位字段、16 位字段、8 位字段依次排列
数组布局 

数组在对象头之后多一个字:

  • 长度 (32bit):元素数量

数组元素紧跟长度字段,按自身类型自然对齐。

String 布局 

java.lang.String对象包含:

  • 对象头(锁字 + 类指针)
  • count (32bit) — 字符数量
  • hash (32bit) — 缓存哈希码
  • 字符数据(内联存储,在字段之后)

String 可以使用 UTF‑16 或者压缩 Latin‑1 编码;编码类型由 count 字段内的标记位标识。

18.1.16 核心数据结构 

ArtMethod

每一个 Java/Kotlin 方法在内部由ArtMethod对象表示(定义于art/runtime/art_method.h第 89 行)。ArtMethod 不是 GC 托管对象,分配在 LinearAlloc,由托管层java.lang.reflect.Method对象持有指针。

// art/runtime/art_method.h, lines 89-99
class EXPORT ArtMethod final {
 public:
  DECLARE_RUNTIME_DEBUG_FLAG(kCheckDeclaringClassState);
  static constexpr uint32_t kRuntimeMethodDexMethodIndex = 0xFFFFFFFF;

  ArtMethod() : access_flags_(0), dex_method_index_(0),
      method_index_(0), hotness_count_(0) { }
  ...
};

关键字段:

  • declaring_class_ — GC 根,指向所属mirror::Class
  • access_flags_ — 方法修饰符与运行时标记(原子访问,支持并发)
  • dex_method_index_ — DEX 文件 method_id 表索引
  • method_index_ — vtable 或者 iftable 索引
  • hotness_count_ — JIT 性能采样计数器
  • ptr_sized_fields_.data_ — 指向 DEX CodeItem(解释模式)或者性能采样信息
  • ptr_sized_fields_.entry_point_from_quick_compiled_code_ — 函数指针,指向编译代码或者跳板函数

入口点字段决定 ART 选用哪一种执行模式:可以指向解释器桥接函数、JIT 编译代码、AOT 编译代码,或者未解析方法的跳板。

ArtMethod 提供大量查询接口读取方法属性:

// art/runtime/art_method.h, lines 168-234 (selected)
bool IsPublic() const { return (GetAccessFlags() & kAccPublic) != 0; }
bool IsPrivate() const { return (GetAccessFlags() & kAccPrivate) != 0; }
bool IsStatic() const { return (GetAccessFlags() & kAccStatic) != 0; }
bool IsConstructor() const { return (GetAccessFlags() & kAccConstructor) != 0; }
bool IsDirect() const {
  constexpr uint32_t direct = kAccStatic | kAccPrivate | kAccConstructor;
  return (GetAccessFlags() & direct) != 0;
}
bool IsSynchronized() const {
  constexpr uint32_t synchonized = kAccSynchronized | kAccDeclaredSynchronized;
  return (GetAccessFlags() & synchonized) != 0;
}
bool IsFinal() const { return (GetAccessFlags() & kAccFinal) != 0; }
bool IsIntrinsic() const { return (GetAccessFlags() & kAccIntrinsic) != 0; }

直接方法与虚方法的区分对方法分发至关重要

  • 直接方法(static、private、构造器):直接调用实现,不需要查虚函数表。
  • 虚方法(其余全部):通过 vtable 或者接口方法表 IMT 分发。
ArtMethod 入口点 ¶

mirror::Class

已加载类的托管对象表示。包含 vtable、iftable 接口表、静态字段数组、实例字段数组、方法数组、类状态(已校验、已初始化等)。作为托管对象,mirror::Class实例存放在 GC 堆,会被 GC 移动与回收。

mirror::Class主要组成:

  • vtableArtMethod*数组,用于虚方法分发
  • iftable — 接口表,映射接口到对应方法数组
  • imt — 接口方法表,基于哈希的快速分发
  • sfields — 静态字段数组
  • ifields — 实例字段数组
  • class_size — 该类实例对象大小
  • status — 类当前状态(已加载、已校验、已初始化等)
  • class_loader — 加载该类的 ClassLoader 引用
  • class_flags — uint32_t 位掩码,供 GC、运行时快速分支判断,无需完整解析类。普通类标记为kClassFlagNormal;其余 bit 标记字符串、数组、引用类、类加载器、dex cache;Android17 新增 record、value 类标记。完整布局与 Android17 对 record 类的修改见 18.11 小节。
DexCache

每个 DEX 文件专属缓存,存放解析完成的字符串、类型、方法、字段。避免重复解析 DEX 文件,加速重复查找。

DexCache 由 ClassLinker 打开 DEX 文件时创建,作为托管mirror::DexCache对象存放在堆中。包含:

  • 已解析字符串数组 — StringIndex 映射到mirror::String*
  • 已解析类型数组 — TypeIndex 映射到mirror::Class*
  • 已解析方法数组 — 方法索引映射到ArtMethod*
  • 已解析字段数组 — 字段索引映射到ArtField*

未解析项存 null(GcRoot<nullptr>),第一次访问时触发解析。

InternTable

驻留表保存java.lang.String的标准实例。调用String.intern()时,如果驻留表已有该字符串,直接返回;否则插入并返回该实例。boot 镜像预先填充高频字符串。

LinearAlloc

指针步进式分配器,用于运行时元数据;元数据不会单独释放,仅在类加载器卸载时整体释放。用于:

  • ArtMethod 数组
  • ArtField 数组
  • 类表
  • IMT 冲突表
  • DexCache 底层存储

18.2 DEX 文件格式 

DEX(Dalvik Executable)是 ART 使用的字节码容器。不同于 Java.class一个文件对应一个类,单个.dex将编译单元全部类打包为一份去重结构,针对内存映射访问做优化。

源码:art/libdexfile/(DEX 解析校验库)。

18.2.1 文件头结构 

DEX 头定义在art/libdexfile/dex/dex_file.h

// art/libdexfile/dex/dex_file.h, lines 134-179
struct Header {
  Magic magic_ = {};              // "dex\n035\0" or similar
  uint32_t checksum_ = 0;        // adler32 of everything except magic and this field
  Sha1 signature_ = {};          // SHA‑1 hash of the rest of the file
  uint32_t file_size_ = 0;       // size of entire file
  uint32_t header_size_ = 0;     // offset to start of next section
  uint32_t endian_tag_ = 0;      // 0x12345678
  uint32_t link_size_ = 0;       // unused
  uint32_t link_off_ = 0;        // unused
  uint32_t map_off_ = 0;         // offset to map list
  uint32_t string_ids_size_ = 0; // number of StringIds
  uint32_t string_ids_off_ = 0;  // file offset of StringIds array
  uint32_t type_ids_size_ = 0;   // number of TypeIds
  uint32_t type_ids_off_ = 0;    // file offset of TypeIds array
  uint32_t proto_ids_size_ = 0;  // number of ProtoIds
  uint32_t proto_ids_off_ = 0;   // file offset of ProtoIds array
  uint32_t field_ids_size_ = 0;  // number of FieldIds
  uint32_t field_ids_off_ = 0;   // file offset of FieldIds array
  uint32_t method_ids_size_ = 0; // number of MethodIds
  uint32_t method_ids_off_ = 0;  // file offset of MethodIds array
  uint32_t class_defs_size_ = 0; // number of ClassDefs
  uint32_t class_defs_off_ = 0;  // file offset of ClassDef array
  uint32_t data_size_ = 0;       // size of data section
  uint32_t data_off_ = 0;        // file offset of data section
};

DEX 版本 41(魔数dex\n041\0)引入容器支持:多个逻辑 DEX 拼接成一块连续数据块;每个逻辑 DEX 增加两个头字段指向外层容器。容器版本常量:DexFile::kDexContainerVersion = 41art/libdexfile/dex/dex_file.h第 112 行);扩展头HeaderV41

// art/libdexfile/dex/dex_file.h, lines 181-184
struct HeaderV41 : public Header {
  uint32_t container_size_ = 0;  // total size of all dex files in the container
  uint32_t header_offset_ = 0;   // offset of this dex's header in the container
};

DexFile::HasDexContainer()在 V41 及以上版本返回 true(容器内仅单个 DEX 也返回 true);GetDexContainerRange()从文件起始位置回退header_offset_字节,读取container_size_得到完整容器范围(art/libdexfile/dex/dex_file.h 291‑303 行)。加载器以此做多条目处理:art/libdexfile/dex/dex_file_loader.h第 113 行,版本大于等于kDexContainerVersion的非主 DEX 作为容器条目打开;读取完毕后断言IsDexContainerLastEntry()(213 行)。Android17 两处加固修改:校验器拒绝声明的container_size_大于实际映射大小的头文件([V41] Check that the claimed size is LE than the actual size);加载器忽略容器尾部多余字节,要求普通 dex 文件只能包含一个容器。V41 与 profile、dex2oat 的交互见 18.11(Android17 变更)。

18.2.2 文件布局 

18.2.3 索引表 

DEX 使用索引表做数据去重。每张表是定长结构体数组,文件各处使用索引引用表项。

StringIdart/libdexfile/dex/dex_file_structs.h第 52 行)

struct StringId {
  uint32_t string_data_off_;  // offset to MUTF‑8 string data
};

TypeId(第 60 行)

struct TypeId {
  dex::StringIndex descriptor_idx_;  // index into string_ids
};

FieldId(第 68 行)

struct FieldId {
  dex::TypeIndex class_idx_;   // defining class
  dex::TypeIndex type_idx_;    // field type
  dex::StringIndex name_idx_;  // field name
};

MethodId(第 89 行)

struct MethodId {
  dex::TypeIndex class_idx_;   // defining class
  dex::ProtoIndex proto_idx_;  // method prototype (return type + parameters)
  dex::StringIndex name_idx_;  // method name
};

ProtoId(第 78 行)

struct ProtoId {
  dex::StringIndex shorty_idx_;     // shorty descriptor (e.g., "VIL")
  dex::TypeIndex return_type_idx_;  // return type
  uint16_t pad_;                    // alignment padding
  uint32_t parameters_off_;         // offset to TypeList for parameters
};

18.2.4 ClassDef 与类数据 

ClassDef结构体(art/libdexfile/dex/dex_file_structs.h第 108 行)定义一个类:

struct ClassDef {
  dex::TypeIndex class_idx_;       // this class's type
  uint16_t pad1_;
  uint32_t access_flags_;          // ACC_PUBLIC, ACC_FINAL, etc.
  dex::TypeIndex superclass_idx_;  // superclass type
  uint16_t pad2_;
  uint32_t interfaces_off_;        // offset to TypeList of interfaces
  dex::StringIndex source_file_idx_;  // source file name
  uint32_t annotations_off_;       // annotations directory
  uint32_t class_data_off_;        // offset to class_data_item
  uint32_t static_values_off_;     // initial static field values
};

class_data_off_指向class_data_item,使用 LEB128 编码存储:

  • 静态字段数量
  • 实例字段数量
  • 直接方法(static、private、构造器)数量
  • 虚方法数量
  • 每个字段:字段索引差值、访问标记
  • 每个方法:方法索引差值、访问标记、code 偏移

18.2.5 CodeItem — 方法字节码 

CodeItem 存放方法真正的 DEX 字节码。标准 DEX 格式包含:

  • registers_size — 使用寄存器总数
  • ins_size — 传入参数寄存器数量
  • outs_size — 调用其他方法使用的输出寄存器数量
  • tries_size — try/catch 块数量
  • debug_info_off — 调试信息偏移
  • insns_size — 16 位指令单元数量
  • insns[] — 字节码指令数组
  • try 项与 catch 处理器数据(tries_size>0 时存在)

18.2.6 Map Item 类型 

DEX 文件末尾 map list 是带类型的目录表。map item 类型码定义于art/libdexfile/dex/dex_file.h190‑211 行:

enum MapItemType : uint16_t {
  kDexTypeHeaderItem               = 0x0000,
  kDexTypeStringIdItem             = 0x0001,
  kDexTypeTypeIdItem               = 0x0002,
  kDexTypeProtoIdItem              = 0x0003,
  kDexTypeFieldIdItem              = 0x0004,
  kDexTypeMethodIdItem             = 0x0005,
  kDexTypeClassDefItem             = 0x0006,
  kDexTypeCallSiteIdItem           = 0x0007,
  kDexTypeMethodHandleItem         = 0x0008,
  kDexTypeMapList                  = 0x1000,
  kDexTypeTypeList                 = 0x1001,
  kDexTypeAnnotationSetRefList     = 0x1002,
  kDexTypeAnnotationSetItem        = 0x1003,
  kDexTypeClassDataItem            = 0x2000,
  kDexTypeCodeItem                 = 0x2001,
  kDexTypeStringDataItem           = 0x2002,
  kDexTypeDebugInfoItem            = 0x2003,
  kDexTypeAnnotationItem           = 0x2004,
  kDexTypeEncodedArrayItem         = 0x2005,
  kDexTypeAnnotationsDirectoryItem = 0x2006,
  kDexTypeHiddenapiClassData       = 0xF000,
};

18.2.7 类型描述符 

DEX 类型描述符遵循 JNI 规范:

描述符 类型
V void
Z boolean
B byte
S short
C char
I int
J long
F float
D double
Ljava/lang/String; 对象引用
[I int 数组
[[Ljava/lang/Object; 二维 Object 数组

18.2.8 Hidden API 数据 

Android P 开始,DEX 格式增加HiddenapiClassData段(art/libdexfile/dex/dex_file_structs.h274 行),为每个字段、方法打上隐藏 API 限制标记。实现灰名单、黑名单管控,阻止应用访问平台内部 API。

18.2.9 MethodHandle 与 CallSite 项 

DEX38 版本及以上支持invokedynamic,依靠:

  • MethodHandleItem(179 行):静态 / 实例 getter、setter、方法调用器的引用
  • CallSiteIdItem(189 行):动态调用的引导方法引用

对 lambda 与其他基于 invokedynamic 的特性是必需的。

18.2.10 DEX 字节码指令 

DEX 字节码使用 16bit 指令单元。一条指令占用 1‑5 个 16bit 字;第一个字低 8bit 为操作码。操作码空间约 256 条指令,分类如下。

Move 指令

00: nop
01: move vA, vB               (4‑bit registers)
02: move/from16 vAA, vBBBB    (8‑bit and 16‑bit registers)
03: move/16 vAAAA, vBBBB      (16‑bit registers)
04: move‑wide vA, vB          (wide move, 64‑bit)
07: move‑object vA, vB        (object reference move)
0a: move‑result vAA           (captures return value)
0b: move‑result‑wide vAA
0c: move‑result‑object vAA
0d: move‑exception vAA        (captures exception in catch)

Return 指令

0e: return‑void
0f: return vAA
10: return‑wide vAA
11: return‑object vAA

Const 常量指令

12: const/4 vA, #+B           (4‑bit constant)
13: const/16 vAA, #+BBBB      (16‑bit constant)
14: const vAA, #+BBBBBBBB     (32‑bit constant)
15: const/high16 vAA, #+BBBB0000
18: const‑wide vAA, #+BBBBBBBBBBBBBBBB  (64‑bit constant)
1a: const‑string vAA, string@BBBB
1c: const‑class vAA, type@BBBB

实例与静态字段操作

52‑58: iget, iget‑wide, iget‑object, iget‑boolean, ...
59‑5f: iput, iput‑wide, iput‑object, iput‑boolean, ...
60‑66: sget, sget‑wide, sget‑object, sget‑boolean, ...
67‑6d: sput, sput‑wide, sput‑object, sput‑boolean, ...

调用指令

6e: invoke‑virtual {vC..vG}, method@BBBB
6f: invoke‑super {vC..vG}, method@BBBB
70: invoke‑direct {vC..vG}, method@BBBB
71: invoke‑static {vC..vG}, method@BBBB
72: invoke‑interface {vC..vG}, method@BBBB
74: invoke‑virtual/range {vCCCC..vNNNN}, method@BBBB

算术逻辑指令

90‑af: add, sub, mul, div, rem, and, or, xor, shl, shr, ushr
       (for int, long, float, double variants)
b0‑cf: add/2addr, sub/2addr, mul/2addr, ...
       (2‑address form: vA = vA op vB)
d0‑d7: add‑int/lit16, rsub‑int, mul‑int/lit16, ...
       (immediate operand forms)

比较与分支

2d‑31: cmpl‑float, cmpg‑float, cmpl‑double, cmpg‑double, cmp‑long
32‑37: if‑eq, if‑ne, if‑lt, if‑ge, if‑gt, if‑le
38‑3d: if‑eqz, if‑nez, if‑ltz, if‑gez, if‑gtz, if‑lez
28‑2a: goto, goto/16, goto/32
2b: packed‑switch
2c: sparse‑switch

数组操作

44‑4a: aget, aget‑wide, aget‑object, aget‑boolean, ...
4b‑51: aput, aput‑wide, aput‑object, aput‑boolean, ...
21: array‑length vA, vB
23: new‑array vA, vB, type@CCCC
24: filled‑new‑array {vC..vG}, type@BBBB

对象操作

1f: check‑cast vAA, type@BBBB
20: instance‑of vA, vB, type@CCCC
22: new‑instance vAA, type@BBBB
27: throw vAA

所有指令操作 DEX 虚拟寄存器(编号 0~registers_size‑1)。最后的ins_size个寄存器是方法传入参数。

18.2.11 MUTF‑8 字符串编码

DEX 文件使用 Modified UTF‑8(MUTF‑8),和标准 UTF‑8 两点区别:

  1. 空字符 U+0000 编码为双字节0xC0 0x80,而不是单字节 0,兼容 C 语言以 0 结尾字符串。
  2. 大于等于 U+10000 的增补字符,编码为两组三字节 UTF‑16 代理对,而不是四字节 UTF‑8。

DEX 字符串数据头部是 ULEB128 编码的长度(UTF‑16 码元数量,不是字节数),末尾跟一个 0 结束符。

18.2.12 LEB128 编码 

DEX 大量使用 LEB128 小端基 128 变长整数,节省小数值存储空间:

  • ULEB128 无符号:每个字节 7bit 数据 + 1bit 延续标记;0‑127 占 1 字节,128‑16383 占 2 字节。
  • SLEB128 有符号:类似,做符号扩展。
  • ULEB128p1:对 value+1 做 ULEB128 编码,用于大量 0 值场景。

LEB128 使用场景:

  • class_data_item(字段、方法计数与差值)
  • 调试信息
  • 注解编码
  • Hidden API 标记

18.2.13 注解 

DEX 支持 Java 注解,三种可见等级(art/libdexfile/dex/dex_file.h231‑235 行):

enum class DexVisibility : uint8_t {
  kBuild         = 0x00,  // visible at build time only
  kRuntime       = 0x01,  // visible at runtime via reflection
  kSystem        = 0x02,  // visible to the runtime system only
};

注解数据组织:

  1. AnnotationsDirectoryItem:每个类的注解总目录
struct AnnotationsDirectoryItem {
  uint32_t class_annotations_off_;
  uint32_t fields_size_;
  uint32_t methods_size_;
  uint32_t parameters_size_;
};
  1. AnnotationSetItem:同一个目标(类、字段、方法、参数)的一组注解
  2. AnnotationItem:单条注解,包含可见标记与编码后的注解数据

注解数据使用带类型标记编码:

// art/libdexfile/dex/dex_file.h, lines 237‑255
kDexAnnotationByte          = 0x00,
kDexAnnotationShort         = 0x02,
kDexAnnotationChar          = 0x03,
kDexAnnotationInt           = 0x04,
kDexAnnotationLong          = 0x06,
kDexAnnotationFloat         = 0x10,
kDexAnnotationDouble        = 0x11,
kDexAnnotationString        = 0x17,
kDexAnnotationType          = 0x18,
kDexAnnotationField         = 0x19,
kDexAnnotationMethod        = 0x1a,
kDexAnnotationEnum          = 0x1b,
kDexAnnotationArray         = 0x1c,
kDexAnnotationAnnotation    = 0x1d,
kDexAnnotationNull          = 0x1e,
kDexAnnotationBoolean       = 0x1f,

18.2.14 调试信息 

DEX 调试信息段保存源码调试相关内容:

  • 行号表:字节码偏移映射源码行号;使用类字
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值