news 2026/8/30 20:11:53

第 18 章:ART 运行时

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第 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.h291‑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 规范:

描述符类型
Vvoid
Zboolean
Bbyte
Sshort
Cchar
Iint
Jlong
Ffloat
Ddouble
Ljava/lang/String;对象引用
[Iint 数组
[[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 调试信息段保存源码调试相关内容:

  • 行号表:字节码偏移映射源码行号;使用类字
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/30 20:07:58

联想校招数据挖掘岗:笔试面试全流程复盘与上岸经验

数据挖掘岗位的校招&#xff0c;我看过太多人把力气用错了地方。2022届那阵子联想校招数据挖掘岗位放出来&#xff0c;不少同学盯着"数据挖掘"四个字就往深度学习、NLP、知识图谱上猛攻&#xff0c;结果一上笔试就傻了眼。这篇文章不绕弯子&#xff0c;直接把我自己从…

作者头像 李华
网站建设 2026/8/30 20:06:58

写论文别只靠大模型,我从选题到答辩用毕业之家收尾

又到论文季。后台被问最多的一句话就是&#xff1a;“到底用哪个AI写论文比较好&#xff1f;” 说实话&#xff0c;2026年了&#xff0c;这个问题的答案早就不是"用ChatGPT就行"。现在的工具已经明显分化成了几个流派&#xff1a;通用大模型负责"想"&#…

作者头像 李华
网站建设 2026/8/30 20:00:20

降ai重复率应该先改哪部分?同段双高和不同章节用两套降重方法?

降ai重复率应该先改哪部分&#xff1f;同段双高和不同章节用两套降重方法&#xff1f; 降ai重复率最容易做错的地方&#xff0c;是看到两个总值都高&#xff0c;就按同一顺序改整篇。真正决定先改哪里的是位置&#xff1a;AI高疑似和查重标红落在同一段&#xff0c;应当共同重…

作者头像 李华
网站建设 2026/8/30 19:56:52

以人为本AI:从感知到行动的6层连接框架详解

之前在服务机器人、无人机视觉感知、智能助手这类项目里做技术落地时&#xff0c;踩过不少“单点很强、整机瘫痪”的坑。视觉模型能检测出画面里的行人和障碍物&#xff0c;大语言模型能流畅对话&#xff0c;底盘控制模块也能正常运动&#xff0c;可一旦把几块能力串起来&#…

作者头像 李华
网站建设 2026/8/30 19:54:36

迈入AI认知时代

2026 AI-GEO新流量革命商业报告&#xff5c;从搜索时代迈入AI认知时代撰写&#xff1a;殷驰 &#xff5c; 一、时代变革&#xff1a;互联网流量规则彻底改写过去十年&#xff0c;企业获客依靠搜索、短视频、付费投流、平台排名。所有流量逻辑&#xff0c;都建立在用户主动找信息…

作者头像 李华
网站建设 2026/8/30 19:53:38

星环科技2024秋招笔试A卷编程题复盘:日志聚合与DAG调度实战

今天想认真聊聊星环科技2024届秋招笔试的A卷编程题。我是在秋招投递大数据平台研发方向时做的这套卷子&#xff0c;考完之后最大的感受是&#xff1a;题目本身不算偏难怪&#xff0c;但“务实”程度非常高&#xff0c;很多题一眼就能看出是从他们自己业务场景里抽象出来的&…

作者头像 李华