Android 虚拟机 ART(Android Runtime)是 Android 平台上的应用程序运行时环境,用于执行应用程序的字节码。ART 自 Android 5.0(Lollipop)开始取代了 Dalvik,成为 Android 的默认运行时环境。本文将从以下几个方面详细系统地介绍 ART,并结合部分源码来探讨其实现原理:
- ART 简介
- ART 与 Dalvik 的对比
- ART 的架构与核心模块
- 关键技术
- Ahead-of-Time 编译 (AOT)
- Just-In-Time 编译 (JIT)
- 垃圾回收 (GC)
- 内存管理与优化
- 源码剖析
- 性能优化的设计理念
- 总结
1. ART 简介
ART 是 Android 平台上的一种高效、稳定的运行时,专为移动设备优化。与 Dalvik 的解释执行不同,ART 结合了提前编译和即时编译,显著提升了应用性能和响应速度,同时降低了内存消耗。
主要特点:
- 高性能:通过 AOT 和 JIT 提高应用运行效率。
- 优化内存使用:改进 GC 算法,减少暂停时间。
- 改进调试工具:支持更强大的调试和分析功能,如 TraceView 和 HPROF。
- 兼容性强:支持运行旧的 Dalvik 字节码。
2. ART 与 Dalvik 的对比
特性 | ART | Dalvik |
---|---|---|
编译模式 | AOT + JIT | 解释执行 + JIT |
初次启动时间 | 较长 | 较短 |
应用运行性能 | 高 | 中等 |
内存消耗 | 较低 | 较高 |
垃圾回收 | 并发、低暂停的 GC | 停止-复制的 GC |
支持工具 | TraceView、Perfetto 等 | 基本调试工具 |
3. ART 的架构与核心模块
ART 的架构分为以下几个核心部分:
- ClassLoader: 负责加载字节码。
- Compiler (AOT/JIT): 提供提前和即时编译功能。
- Garbage Collector (GC): 负责内存分配与回收。
- Runtime: 提供运行时环境,管理线程、类、方法调用等。
- Debugger 和 Profiler: 提供调试和性能分析工具。
下面是架构图示意:
4. 关键技术
4.1 Ahead-of-Time 编译 (AOT)
Android Runtime (ART) 的 Ahead-of-Time (AOT) 编译 是一种在应用安装时,将字节码(Bytecode)直接编译为机器码(Native Code)的技术,从而提升应用运行时的性能。下面详细说明:
背景与原理
- Dalvik VM 的问题在 Android ART 之前,Android 使用的是 Dalvik 虚拟机,它主要依赖 Just-in-Time (JIT) 编译技术。在应用运行时,字节码被即时编译为机器码,尽管灵活性较高,但存在以下问题:
- 启动速度慢:每次启动应用时,字节码需要重新编译。
- 内存占用高:需要维护额外的 JIT 缓存。
- 性能不稳定:即时编译会增加 CPU 负载,影响用户体验。
- ART 的改进:引入 AOT 编译ART 使用 AOT 技术,在应用安装时直接编译字节码为机器码并保存。应用运行时不需要即时编译,直接执行机器码,大大提升了性能。
工作原理
AOT 编译发生在应用安装过程中,主要步骤包括:
- 字节码加载从应用的
.dex
文件(包含应用代码的 Dalvik Executable 格式)加载字节码。 - 静态编译使用编译器(如
dex2oat
)将字节码转换为目标设备的机器码。 - 生成 OAT 文件编译后的机器码保存在 OAT 文件中(Optimized Android Runtime 文件),这些文件通常存储在设备的
/data/dalvik-cache
路径下。 - 优化与校验
- ART 在编译过程中会根据设备的 CPU 架构(如 ARM 或 x86)生成优化的机器码。
- 还会执行验证(Verification),确保编译后的代码不会造成崩溃或安全问题。
优点
- 启动更快由于机器码已经编译好,应用启动时无需等待即时编译。
- 性能更高
- 减少运行时的 JIT 编译负担,提升应用流畅性。
- 更高效的内存使用,因为无需维护 JIT 缓存。
- 节能省电运行时的 CPU 开销减少,有助于延长电池续航。
缺点
- 安装时间更长 AOT 编译会显著延长应用安装时间,因为需要完成编译和优化过程。
- 存储空间占用大编译后的 OAT 文件会占用额外的存储空间。
- 灵活性降低动态语言特性(如反射)和运行时修改代码的能力会受到一定限制。
ART 的 AOT 与 JIT 的结合
在 Android 7.0(Nougat)及之后的版本中,ART 引入了 混合编译模式,将 AOT 与 JIT 优势结合:
- 应用安装时只进行部分 AOT 编译(主要是核心代码)。
- 运行时,使用 JIT 编译未优化的代码片段,同时记录 JIT 编译结果。
- 应用闲置时,通过后台编译(Profile-Guided Optimization, PGO),将热点代码进一步转为 AOT 编译,提高性能。
代码示例与工具
- dex2oat 工具
在 Android 系统中,dex2oat
是主要的 AOT 编译工具。你可以使用以下命令查看相关信息:
dex2oat --help
- 检查 AOT 文件
运行设备上的命令,查看已生成的 OAT 文件:
ls /data/dalvik-cache/
- 关键函数 art/compiler
bool CompilerDriver::CompileAll() {// 遍历所有的类和方法for (Class c : classes) {for (Method m : c.GetMethods()) {CompileMethod(m);}}return true;
}
总结
ART 的 AOT 编译通过将字节码提前编译为机器码,显著提升了 Android 应用的启动速度和运行性能。尽管有安装时间较长、存储占用等问题,但通过与 JIT 的结合,ART 在后续版本中进一步优化了运行效率和用户体验,是 Android 性能优化的里程碑技术。
4.2 Just-In-Time 编译 (JIT)
Android Runtime (ART) 的 Just-In-Time (JIT) 编译 是在应用运行时动态编译字节码为机器码的技术,与 Ahead-of-Time (AOT) 编译互为补充,用于提升运行效率并减少应用安装时间。以下是 JIT 编译的详细介绍。
JIT 编译的背景
- Dalvik VM 的传统 JIT在 ART 之前,Android 使用的是 Dalvik VM,其核心编译方式就是 JIT。JIT 的特点是按需即时编译,虽然减少了安装时的编译负担,但运行时性能受限,具体表现为:
- 每次运行时都需要重新编译。
- 占用额外的 CPU 和内存资源,导致运行时性能波动。
- ART 的早期缺陷:全量 AOT 编译在早期 ART(Android 5.0 和 6.0)中,全量 AOT 编译优化了运行时性能,但增加了应用安装时间,且存储占用较高。为平衡性能与灵活性,Android 在 7.0(Nougat)引入了 JIT + AOT 混合模式。
JIT 编译的工作原理
ART 中的 JIT 编译在运行时按需触发,以下是其主要工作流程:
- 字节码解释执行
- 应用启动时,ART 会使用解释器逐条执行字节码,快速响应用户请求。
- 如果某些代码片段(如循环或常用方法)被多次调用,ART 会将其标记为热点代码。
- 热点代码的动态编译
- 当热点代码的执行次数超过一定阈值时,JIT 编译器会将其即时编译为机器码。
- 编译后的机器码保存在内存中,并直接执行,提高运行效率。
- Profile 文件记录
- 在应用运行过程中,ART 会将热点代码的信息记录到 Profile 文件中。
- 这些 Profile 数据可用于后续优化,如后台编译(Profile-Guided Optimization,PGO)。
- 后台优化与 AOT 的结合
- 应用空闲时,ART 使用 JIT 记录的 Profile 数据进行增量 AOT 编译,将热点代码进一步优化为高效的机器码。
JIT 的优点
- 灵活性高
- 仅对频繁使用的代码进行编译,减少了不必要的编译开销。
- 支持动态语言特性(如反射)和运行时动态加载。
- 安装时间短
- 应用安装时无需进行全量 AOT 编译,安装速度更快。
- 优化运行性能
- 动态编译热点代码,提升执行效率。
- 配合 Profile 数据进行长期优化。
JIT 的缺点
- 启动速度相对慢
- 初次执行代码时,仍需解释执行,直到热点代码触发编译。
- 内存占用高
- 编译后的机器码保存在内存中,占用更多 RAM。
- 性能波动
- 编译过程需要消耗 CPU 资源,在高负载情况下可能影响用户体验。
JIT + AOT 的混合模式
Android 7.0(Nougat)及之后版本中,ART 实现了 JIT 与 AOT 的混合编译模式,以兼顾运行效率和灵活性:
- 运行时使用 JIT 编译
- 应用初次运行时,通过 JIT 编译热点代码,快速优化性能。
- 后台优化 AOT 编译
- 应用空闲时,利用 Profile 数据增量编译机器码,生成优化的 OAT 文件,供后续运行使用。
- 适应设备环境
- JIT 和 AOT 编译结果均针对设备的 CPU 架构优化,提供更好的运行效率。
示例与工具
- Profile 文件查看Profile 文件通常存储在以下路径:
/data/data//code_cache/profile/
- ART 的优化工具使用
profman
工具分析 Profile 数据:
profman --dump-profile-info --profile-file=
- 实时调试 JIT 编译开启 ART 的 JIT 调试日志:
setprop dalvik.vm.extra-opts -verbose:jit
- JIT 编译示例代码 art/runtime/jit
void Jit::CompileMethod(Method* method) {if (IsHotMethod(method)) {CompileToMachineCode(method);}
}
总结
ART 的 JIT 编译通过动态编译热点代码,提供了高效灵活的性能优化方式,与 AOT 编译相辅相成。在应用安装、运行和优化的不同阶段,JIT 编译减少了安装时间,提升了运行性能,同时为后台 AOT 优化提供了数据支持。这种混合编译模式在 Android 系统中实现了性能与灵活性的良好平衡。
4.3 垃圾回收 (GC)
Android Runtime (ART) 的垃圾回收(Garbage Collection, GC)机制负责自动管理应用的内存,释放不再使用的对象所占用的空间,从而避免内存泄漏和提升性能。ART 的垃圾回收相较于 Dalvik 虚拟机有显著改进,以下是详细介绍:
ART 垃圾回收的基本原理
- 对象分配与生命周期
- ART 管理的内存堆分为不同区域(如年轻代和老年代)。
- 应用运行时在堆上分配内存,对象的生命周期决定了它被回收的时机。
- 垃圾回收触发条件垃圾回收的触发通常基于以下条件:
- 堆内存不足:新对象分配时发现堆已满。
- 手动触发:某些 API(如 System.gc())可能提示进行回收。
- 内存优化:系统主动回收内存以提升性能。
- 标记-清除算法ART 的 GC 使用 标记-清除(Mark-and-Sweep) 算法,分为以下步骤:
- 标记阶段:找到所有活跃的对象(可达的对象)。
- 清除阶段:释放未标记(不可达)的对象所占的内存。
ART 垃圾回收的特点
- 并发垃圾回收ART 使用并发 GC,将垃圾回收的部分任务分摊到多个线程中,从而减少对主线程的影响,提高应用的流畅性。
- 低延迟设计
- ART 针对交互式应用进行了优化,尽可能缩短 GC 的暂停时间(GC Pause Time)。
- 在用户操作频繁的时刻(如滑动列表),ART 尽量避免大规模的回收操作。
- 分代回收ART 使用分代式垃圾回收策略,将堆分为以下几部分:
- 年轻代(Young Generation):存放生命周期短的对象,回收频率高。
- 老年代(Old Generation):存放生命周期长的对象,回收频率低。
- 增量回收ART 的 GC 还支持增量回收(Incremental GC),将一次性的大量回收操作分解为小批量的任务,降低应用响应的卡顿。
ART 的垃圾回收类型
ART 提供多种垃圾回收类型,根据需求自动切换:
- Partial GC(部分回收)
- 仅回收年轻代对象。
- 触发快,暂停时间短。
- Full GC(完全回收)
- 回收整个堆,包括年轻代和老年代。
- 通常在堆内存不足时触发,暂停时间较长。
- Concurrent GC(并发回收)
- 在后台线程执行垃圾回收。
- 减少对主线程的影响,提高性能。
- Compacting GC(压缩回收)
- 重新整理内存堆,将分散的内存块合并为连续的内存。
- 提高后续对象分配的效率,避免内存碎片问题。
- Sticky GC(粘性回收)
- 仅清理应用运行期间新分配的短生命周期对象。
- 触发快,适用于频繁分配对象的场景(如动画或 UI 渲染)。
ART GC 的优化
- 避免频繁触发 GC
- 频繁分配短生命周期对象可能导致频繁的垃圾回收,从而影响性能。
- 优化方法:减少临时对象的创建,尤其是在循环中。
- 减小堆内存压力
- 控制对象大小,避免一次性分配过多大对象(如 Bitmap)。
- 优化方法:使用 Bitmap.recycle() 主动释放资源。
- 监控 GC 行为
- 通过 Android Studio Profiler 或 adb logcat 查看 GC 日志,了解回收频率和暂停时间:
adb logcat | grep GC
- 调整堆大小
- AndroidManifest.xml 中通过 android:largeHeap 属性申请更大的堆内存,但仅在必要时使用。
- 示例:
<applicationandroid:largeHeap="true">
</application>
GC 日志示例
以下是 ART GC 的典型日志:
GC concurrent freed 2048K, 10% free 20480K/22528K, paused 5ms+2ms, total 10ms
日志含义:
- GC concurrent:并发回收。
- freed 2048K:释放了 2MB 内存。
- 10% free:堆剩余 10% 的空闲空间。
- 20480K/22528K:堆当前使用 20MB,总大小为 22MB。
- paused 5ms+2ms:主线程暂停时间分别为 5ms 和 2ms。
- total 10ms:总回收时间为 10ms。
总结
ART 的垃圾回收机制通过分代式设计、并发回收和增量回收等方式,实现了低延迟、高效的内存管理。这种机制在减少内存泄漏、优化性能方面有显著效果,但开发者仍需遵循最佳实践(如减少对象分配和合理使用内存)来配合 GC 的高效运行,从而保证应用的流畅性和稳定性。
4.4 内存管理与优化
Android Runtime (ART) 的内存模型和优化机制是其高性能运行的核心。相比于早期的 Dalvik 虚拟机,ART 在内存管理和优化方面做出了许多改进,使得应用运行更加高效、流畅且节能。以下是 ART 的内存模型及其优化原理的详细介绍:
ART 的内存模型
ART 的内存模型由以下几个主要部分组成:
1. 堆内存
堆是 ART 中分配对象的主要区域,由以下几个部分组成:
- 年轻代(Young Generation): 存储生命周期较短的对象。包括 Eden 区和两个 Survivor 区。对象大多在此区域被创建和回收。采用高频率的垃圾回收机制,回收快,暂停时间短。
- 老年代(Old Generation): 存储生命周期较长的对象(多次在年轻代垃圾回收中幸存)。回收频率低,但占用空间大。通常在 Full GC 时进行回收。
2. 栈内存
- 每个线程都有自己的独立栈,用于存储局部变量、方法调用信息等。
- 栈内存生命周期与线程一致,线程结束时栈内存自动释放。
3. 方法区
- 用于存储类的元信息(如方法、字段等)。
- 在 ART 中,这部分被称为 Class Metadata,其管理更加高效且占用较少内存。
4. 本地内存(Native Heap)
- 由 JNI 或底层库分配,与 ART 的堆分开管理。
- 主要用于直接与操作系统交互的资源(如 Bitmap、OpenGL 等)。
ART 内存管理的关键特性
1. 分代式内存管理
ART 实现了分代式内存管理,针对不同生命周期的对象采用不同的策略:
- 短生命周期对象(年轻代): 采用高效的复制垃圾回收(Copying GC)算法,快速回收。
- 长生命周期对象(老年代): 使用标记-清除(Mark-and-Sweep)算法进行回收,减少停顿时间。
- 粘性垃圾回收(Sticky GC): 仅回收最近分配的短生命周期对象,适用于频繁内存分配的场景。
2. 内存对齐与压缩
ART 优化了内存分配的方式:
- 对象内存对齐: 保证对象存储在对齐的地址上,提升访问效率。
- 指针压缩(Pointer Compression): 在 64 位架构下,通过压缩指针(将 64 位指针压缩为 32 位),减少内存占用。
3. 增量分配与回收
- ART 引入增量内存分配和回收机制,将内存操作分散到多个时刻,降低单次分配或回收的延迟。
4. 垃圾回收优化
- 并发垃圾回收(Concurrent GC): 在后台线程完成标记和清除工作,减少主线程的阻塞时间。
- 压缩垃圾回收(Compacting GC): 防止内存碎片,通过整理内存堆空间提升后续分配效率。
ART 内存优化的核心技术
1. 即时与延迟分配
ART 在对象分配时,使用线程本地分配缓冲区(Thread Local Allocation Buffer, TLAB):
- 分配效率提升: 每个线程独立分配小块内存,减少锁竞争。
- 减少全局锁冲突: 提高多线程环境下的内存分配效率。
2. 内存释放与复用
ART 优化了内存释放的机制:
- 回收后的内存块会直接进入内存池,供后续对象分配复用,减少操作系统层的内存申请。
3. 代码与数据共享
- Dex 文件优化: ART 将 .dex 文件编译为更高效的 OAT 文件,减少内存消耗。
- 字符串池: ART 实现了全局字符串池,共享重复字符串以减少内存占用。
4. 多用户优化
- ART 针对 Android 多用户模式优化了资源共享机制,例如系统类的元信息仅加载一次并共享。
ART 内存模型优化的效果
1. 提高运行性能
- 减少垃圾回收引起的停顿时间,提升应用响应速度。
- 增强内存分配效率,特别是高频次对象分配场景(如 UI 渲染)。
2. 降低内存占用
- 通过指针压缩、字符串池等技术,减少应用的内存足迹。
- 内存复用机制避免了频繁的内存分配和回收。
3. 提升设备续航
- 更高效的内存管理降低了 CPU 和内存的使用率,从而减少电池消耗。
开发者优化内存的最佳实践
1. 避免内存泄漏
- Context 管理: 避免长生命周期对象持有短生命周期的 Context。
- 工具检测: 使用 Android Studio Profiler 或 LeakCanary 检测内存泄漏。
2. 减少临时对象分配
- 避免在循环中频繁分配短生命周期对象。
- 使用对象池(Object Pool)复用高频创建的对象。
3. 优化大对象使用
- 对于 Bitmap 等大对象,使用 Bitmap.recycle() 主动释放内存。
- 尽量使用合适的压缩格式(如 WebP)降低图片大小。
4. 使用 Android 平台特性
- 配置适当的 android:largeHeap 仅在必要时使用。
- 利用 Profile-Guided Optimization (PGO) 提升运行时效率。
总结
ART 的内存模型通过分代管理、并发回收、压缩优化等技术,提供了高效的内存分配和回收机制,减少了内存泄漏和性能瓶颈。此外,ART 针对特定设备和应用场景的优化(如指针压缩和 TLAB)显著提升了应用的运行效率。开发者需结合 ART 的内存特性,配合最佳实践,确保应用在各种设备上的内存使用和性能表现达到最佳状态。
5. 源码剖析
5.1 ART 初始化
ART 在启动时初始化环境,包括类加载器、GC 和编译器等。
源码路径:
- art/runtime/runtime.cc
关键函数:
void Runtime::Init() {// 初始化 GCInitGC();// 初始化编译器InitCompiler();// 加载基本类LoadBaseClasses();
}
5.2 方法执行
ART 通过解释器或编译器执行方法。
源码路径:
- art/runtime/interpreter/interpreter.cc
核心代码:
void Interpreter::ExecuteMethod(Thread* thread, Method* method) {if (method->IsCompiled()) {ExecuteCompiledCode(thread, method);} else {ExecuteDexCode(thread, method);}
}
6. 性能优化的设计理念
- 冷热分离:通过 JIT 编译和 Profiling 分析,将热方法进行动态优化。
- 减少停顿时间:通过并发 GC 减少应用的响应延迟。
- 内存优化:利用 TLAB 和大型对象空间优化内存分配。
7. 总结
ART 作为 Android 平台的核心运行时环境,大幅度提升了应用性能和用户体验。它的 AOT 和 JIT 编译器、并发垃圾回收和先进的内存管理为 Android 应用的高效运行提供了强有力的保障。
通过分析 ART 源码,我们能够深入理解其设计理念和实现细节,为开发高性能 Android 应用打下坚实基础。如果您有更多深入学习的需求,可以进一步探索 ART 的调试工具和优化方法。
参考
Memory Management in Android
Android CPU, Compilers, D8 & R8