Android ART知多少?

news/2024/11/16 16:30:19 标签: android, 虚拟机, ART

Android 虚拟机 ART(Android Runtime)是 Android 平台上的应用程序运行时环境,用于执行应用程序的字节码。ART 自 Android 5.0(Lollipop)开始取代了 Dalvik,成为 Android 的默认运行时环境。本文将从以下几个方面详细系统地介绍 ART,并结合部分源码来探讨其实现原理:

  1. ART 简介
  2. ART 与 Dalvik 的对比
  3. ART 的架构与核心模块
  4. 关键技术
    • Ahead-of-Time 编译 (AOT)
    • Just-In-Time 编译 (JIT)
    • 垃圾回收 (GC)
    • 内存管理与优化
  5. 源码剖析
  6. 性能优化的设计理念
  7. 总结

ART__13">1. ART 简介

ART 是 Android 平台上的一种高效、稳定的运行时,专为移动设备优化。与 Dalvik 的解释执行不同,ART 结合了提前编译和即时编译,显著提升了应用性能和响应速度,同时降低了内存消耗。

主要特点:

  • 高性能:通过 AOT 和 JIT 提高应用运行效率。
  • 优化内存使用:改进 GC 算法,减少暂停时间。
  • 改进调试工具:支持更强大的调试和分析功能,如 TraceView 和 HPROF。
  • 兼容性强:支持运行旧的 Dalvik 字节码。

ART__Dalvik__23">2. ART 与 Dalvik 的对比

特性ARTDalvik
编译模式AOT + JIT解释执行 + JIT
初次启动时间较长较短
应用运行性能中等
内存消耗较低较高
垃圾回收并发、低暂停的 GC停止-复制的 GC
支持工具TraceView、Perfetto 等基本调试工具

ART__34">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)的技术,从而提升应用运行时的性能。下面详细说明:

背景与原理
  1. Dalvik VM 的问题在 Android ART 之前,Android 使用的是 Dalvik 虚拟机,它主要依赖 Just-in-Time (JIT) 编译技术。在应用运行时,字节码被即时编译为机器码,尽管灵活性较高,但存在以下问题:
  • 启动速度慢:每次启动应用时,字节码需要重新编译。
  • 内存占用高:需要维护额外的 JIT 缓存。
  • 性能不稳定:即时编译会增加 CPU 负载,影响用户体验。
  1. ART 的改进:引入 AOT 编译ART 使用 AOT 技术,在应用安装时直接编译字节码为机器码并保存。应用运行时不需要即时编译,直接执行机器码,大大提升了性能。

工作原理

AOT 编译发生在应用安装过程中,主要步骤包括:

  1. 字节码加载从应用的 .dex 文件(包含应用代码的 Dalvik Executable 格式)加载字节码。
  2. 静态编译使用编译器(如 dex2oat)将字节码转换为目标设备的机器码。
  3. 生成 OAT 文件编译后的机器码保存在 OAT 文件中(Optimized Android Runtime 文件),这些文件通常存储在设备的 /data/dalvik-cache 路径下。
  4. 优化与校验
  • ART 在编译过程中会根据设备的 CPU 架构(如 ARM 或 x86)生成优化的机器码。
  • 还会执行验证(Verification),确保编译后的代码不会造成崩溃或安全问题。

优点

  1. 启动更快由于机器码已经编译好,应用启动时无需等待即时编译。
  2. 性能更高
  • 减少运行时的 JIT 编译负担,提升应用流畅性。
  • 更高效的内存使用,因为无需维护 JIT 缓存。
  1. 节能省电运行时的 CPU 开销减少,有助于延长电池续航。

缺点

  1. 安装时间更长 AOT 编译会显著延长应用安装时间,因为需要完成编译和优化过程。
  2. 存储空间占用大编译后的 OAT 文件会占用额外的存储空间。
  3. 灵活性降低动态语言特性(如反射)和运行时修改代码的能力会受到一定限制。

ART__AOT__JIT__85">ART 的 AOT 与 JIT 的结合

在 Android 7.0(Nougat)及之后的版本中,ART 引入了 混合编译模式,将 AOT 与 JIT 优势结合:

  • 应用安装时只进行部分 AOT 编译(主要是核心代码)。
  • 运行时,使用 JIT 编译未优化的代码片段,同时记录 JIT 编译结果。
  • 应用闲置时,通过后台编译(Profile-Guided Optimization, PGO),将热点代码进一步转为 AOT 编译,提高性能。

代码示例与工具

  1. dex2oat 工具
    在 Android 系统中,dex2oat 是主要的 AOT 编译工具。你可以使用以下命令查看相关信息:
dex2oat --help
  1. 检查 AOT 文件
    运行设备上的命令,查看已生成的 OAT 文件:
ls /data/dalvik-cache/
  1. 关键函数 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 编译的背景

  1. Dalvik VM 的传统 JITART 之前,Android 使用的是 Dalvik VM,其核心编译方式就是 JIT。JIT 的特点是按需即时编译,虽然减少了安装时的编译负担,但运行时性能受限,具体表现为:
  • 每次运行时都需要重新编译。
  • 占用额外的 CPU 和内存资源,导致运行时性能波动。
  1. ART 的早期缺陷:全量 AOT 编译在早期 ART(Android 5.0 和 6.0)中,全量 AOT 编译优化了运行时性能,但增加了应用安装时间,且存储占用较高。为平衡性能与灵活性,Android 在 7.0(Nougat)引入了 JIT + AOT 混合模式

JIT 编译的工作原理

ART 中的 JIT 编译在运行时按需触发,以下是其主要工作流程:

  1. 字节码解释执行
  • 应用启动时,ART 会使用解释器逐条执行字节码,快速响应用户请求。
  • 如果某些代码片段(如循环或常用方法)被多次调用,ART 会将其标记为热点代码。
  1. 热点代码的动态编译
  • 当热点代码的执行次数超过一定阈值时,JIT 编译器会将其即时编译为机器码。
  • 编译后的机器码保存在内存中,并直接执行,提高运行效率。
  1. Profile 文件记录
  • 在应用运行过程中,ART 会将热点代码的信息记录到 Profile 文件中。
  • 这些 Profile 数据可用于后续优化,如后台编译(Profile-Guided Optimization,PGO)。
  1. 后台优化与 AOT 的结合
  • 应用空闲时,ART 使用 JIT 记录的 Profile 数据进行增量 AOT 编译,将热点代码进一步优化为高效的机器码。

JIT 的优点

  1. 灵活性高
  • 仅对频繁使用的代码进行编译,减少了不必要的编译开销。
  • 支持动态语言特性(如反射)和运行时动态加载。
  1. 安装时间短
  • 应用安装时无需进行全量 AOT 编译,安装速度更快。
  1. 优化运行性能
  • 动态编译热点代码,提升执行效率。
  • 配合 Profile 数据进行长期优化。

JIT 的缺点

  1. 启动速度相对慢
  • 初次执行代码时,仍需解释执行,直到热点代码触发编译。
  1. 内存占用高
  • 编译后的机器码保存在内存中,占用更多 RAM。
  1. 性能波动
  • 编译过程需要消耗 CPU 资源,在高负载情况下可能影响用户体验。

JIT + AOT 的混合模式

Android 7.0(Nougat)及之后版本中,ART 实现了 JIT 与 AOT 的混合编译模式,以兼顾运行效率和灵活性:

  1. 运行时使用 JIT 编译
  • 应用初次运行时,通过 JIT 编译热点代码,快速优化性能。
  1. 后台优化 AOT 编译
  • 应用空闲时,利用 Profile 数据增量编译机器码,生成优化的 OAT 文件,供后续运行使用。
  1. 适应设备环境
  • JIT 和 AOT 编译结果均针对设备的 CPU 架构优化,提供更好的运行效率。

示例与工具

  1. Profile 文件查看Profile 文件通常存储在以下路径:
/data/data//code_cache/profile/
  1. ART 的优化工具使用 profman 工具分析 Profile 数据:
profman --dump-profile-info --profile-file=
  1. 实时调试 JIT 编译开启 ART 的 JIT 调试日志:
setprop dalvik.vm.extra-opts -verbose:jit
  1. 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__211">ART 垃圾回收的基本原理

  1. 对象分配与生命周期
  • ART 管理的内存堆分为不同区域(如年轻代和老年代)。
  • 应用运行时在堆上分配内存,对象的生命周期决定了它被回收的时机。
  1. 垃圾回收触发条件垃圾回收的触发通常基于以下条件:
  • 堆内存不足:新对象分配时发现堆已满。
  • 手动触发:某些 API(如 System.gc())可能提示进行回收。
  • 内存优化:系统主动回收内存以提升性能。
  1. 标记-清除算法ART 的 GC 使用 标记-清除(Mark-and-Sweep) 算法,分为以下步骤:
  • 标记阶段:找到所有活跃的对象(可达的对象)。
  • 清除阶段:释放未标记(不可达)的对象所占的内存。
    在这里插入图片描述

ART__225">ART 垃圾回收的特点

  1. 并发垃圾回收ART 使用并发 GC,将垃圾回收的部分任务分摊到多个线程中,从而减少对主线程的影响,提高应用的流畅性。
  2. 低延迟设计
  • ART 针对交互式应用进行了优化,尽可能缩短 GC 的暂停时间(GC Pause Time)。
  • 在用户操作频繁的时刻(如滑动列表),ART 尽量避免大规模的回收操作。
  1. 分代回收ART 使用分代式垃圾回收策略,将堆分为以下几部分:
  • 年轻代(Young Generation):存放生命周期短的对象,回收频率高。
  • 老年代(Old Generation):存放生命周期长的对象,回收频率低。
  1. 增量回收ART 的 GC 还支持增量回收(Incremental GC),将一次性的大量回收操作分解为小批量的任务,降低应用响应的卡顿。

ART__235">ART 的垃圾回收类型

ART 提供多种垃圾回收类型,根据需求自动切换:

  1. Partial GC(部分回收)
  • 仅回收年轻代对象。
  • 触发快,暂停时间短。
  1. Full GC(完全回收)
  • 回收整个堆,包括年轻代和老年代。
  • 通常在堆内存不足时触发,暂停时间较长。
  1. Concurrent GC(并发回收)
  • 在后台线程执行垃圾回收。
  • 减少对主线程的影响,提高性能。
  1. Compacting GC(压缩回收)
  • 重新整理内存堆,将分散的内存块合并为连续的内存。
  • 提高后续对象分配的效率,避免内存碎片问题。
  1. Sticky GC(粘性回收)
  • 仅清理应用运行期间新分配的短生命周期对象。
  • 触发快,适用于频繁分配对象的场景(如动画或 UI 渲染)。

ART_GC__254">ART GC 的优化

  1. 避免频繁触发 GC
  • 频繁分配短生命周期对象可能导致频繁的垃圾回收,从而影响性能。
  • 优化方法:减少临时对象的创建,尤其是在循环中。
  1. 减小堆内存压力
  • 控制对象大小,避免一次性分配过多大对象(如 Bitmap)。
  • 优化方法:使用 Bitmap.recycle() 主动释放资源。
  1. 监控 GC 行为
  • 通过 Android Studio Profiler 或 adb logcat 查看 GC 日志,了解回收频率和暂停时间:
adb logcat | grep GC
  1. 调整堆大小
  • AndroidManifest.xml 中通过 android:largeHeap 属性申请更大的堆内存,但仅在必要时使用。
  • 示例:
<application
    android: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__305">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__327">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__349">ART 内存优化的核心技术

1. 即时与延迟分配

ART 在对象分配时,使用线程本地分配缓冲区(Thread Local Allocation Buffer, TLAB):

  • 分配效率提升: 每个线程独立分配小块内存,减少锁竞争。
  • 减少全局锁冲突: 提高多线程环境下的内存分配效率。

2. 内存释放与复用

ART 优化了内存释放的机制:

  • 回收后的内存块会直接进入内存池,供后续对象分配复用,减少操作系统层的内存申请。

3. 代码与数据共享

  • Dex 文件优化: ART 将 .dex 文件编译为更高效的 OAT 文件,减少内存消耗。
  • 字符串池: ART 实现了全局字符串池,共享重复字符串以减少内存占用。

4. 多用户优化

  • ART 针对 Android 多用户模式优化了资源共享机制,例如系统类的元信息仅加载一次并共享。

ART__369">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. 源码剖析

ART__409">5.1 ART 初始化

ART 在启动时初始化环境,包括类加载器、GC 和编译器等。

源码路径:

  • art/runtime/runtime.cc
    关键函数:
void Runtime::Init() {
    // 初始化 GC
    InitGC();
    // 初始化编译器
    InitCompiler();
    // 加载基本类
    LoadBaseClasses();
}
5.2 方法执行

ART 通过解释器或编译器执行方法。

源码路径:

  • art/runtime/interpreter/interpreter.cc
    核心代码:
void Interpreter::ExecuteMethod(Thread* thread, Method* method) {
    if (method-&gt;IsCompiled()) {
        ExecuteCompiledCode(thread, method);
    } else {
        ExecuteDexCode(thread, method);
    }
}

6. 性能优化的设计理念

  1. 冷热分离:通过 JIT 编译和 Profiling 分析,将热方法进行动态优化。
  2. 减少停顿时间:通过并发 GC 减少应用的响应延迟。
  3. 内存优化:利用 TLAB 和大型对象空间优化内存分配。

7. 总结

ART 作为 Android 平台的核心运行时环境,大幅度提升了应用性能和用户体验。它的 AOT 和 JIT 编译器、并发垃圾回收和先进的内存管理为 Android 应用的高效运行提供了强有力的保障。

通过分析 ART 源码,我们能够深入理解其设计理念和实现细节,为开发高性能 Android 应用打下坚实基础。如果您有更多深入学习的需求,可以进一步探索 ART 的调试工具和优化方法。

参考

Memory Management in Android
Android CPU, Compilers, D8 & R8


http://www.niftyadmin.cn/n/5754389.html

相关文章

Flutter:Widget生命周期

StatelessWidget&#xff1a;无状态部件的生命周期 import package:flutter/material.dart;void main() {runApp(App()); }class App extends StatelessWidget {overrideWidget build(BuildContext context) {return MaterialApp(home: MyHomePage(title: MyHome),);} }class M…

vue3: ref, reactive, readonly, shallowReactive

vue3: ref, reactive, readonly, shallowReactive 原文地址:https://mp.weixin.qq.com/s/S3jPZKEMBP8nQQObF5d2VA <template><!-- <ul><li v-for"item in list.arr">{{item}}</li></ul><button click.prevent"add"…

数据结构概述及线性结构

1.数据结构研究的两个东西&#xff1a;逻辑结构和存储结构 2.逻辑结构&#xff1a; 线性&#xff1a;1&#xff1a;1,表。例如每个学生都有唯一的学号。顺序表&#xff0c;链式表。非线性&#xff1a;层级关系&#xff08;1&#xff1a;n&#xff09;&#xff0c;树。例如电脑…

Opengl光照测试

代码 #include "Model.h" #include "shader_m.h" #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" //以上是放在同目录的头文件#include <glad/glad.h> #include <GLFW/glfw3.…

PySpark3:Row对象常见操作以及Row、RDD、DataFrame互相转换

目录 一、Row对象常见操作 二、Row、RDD、DataFrame互相转换 1、RDD—>DataFrame 2、DataFrame—>RDD 3、DataFrame—>Row 4、Row—>DataFrame 一、Row对象常见操作 from pyspark.sql import Row# 创建一个Row对象 row Row(name"张三", age25)# …

.NET 9 - BinaryFormatter移除

1.简单介绍 .NET 9 SDK正式版已经发布, 下载地址是.NET9 同时.NET Conf 2024 大会已经从2024-11-13开始了&#xff0c;感觉Aspire和AI的内容相对挺多的&#xff0c;主题分享演示时候打开的网站大部分都是Blazor制作的。 这次.NET Conf 2024老师也再次说明了一下&#xff0c;…

1 图的搜索 奇偶剪枝

图论——图的搜索_Alex_McAvoy的博客-CSDN博客 语雀版本 1 深度优先搜索DFS 1. 从图中某个顶点 v0 出发&#xff0c;首先访问 v0 2. 访问结点 v0 的第一个邻接点&#xff0c;以这个邻接点 vt 作为一个新节点&#xff0c;访问 vt 所有邻接点&#xff0c;直到以 vt 出发的所有节…

回顾二维数组——数组指针部分

数组指针才真正等同于二维数组名 数组指针&#xff1a; 当运行下面的代码的时候&#xff0c;会有警告&#xff0c;偏移量不同&#xff0c;arr偏移的是整行数组&#xff0c;与注释的p不同&#xff0c;如果p&arr[0][0],p表示的就是依次、连续的12个元素地址&#xff0c;偏移…