小知识,大挑战!本文正在参与「程序员必备小知识」创作活动
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。
1. Dalvik
Dalvik是Google公司自己设计用于Android平台的虚拟机。它可以支持已转换为 .dex(即Dalvik Executable)格式的Java应用程序的运行,.dex 格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。
在 Android L (Android 5.0) 之前叫作 DVM,5.0 之后直接删除DVM,代替它的是传闻已久的ART(Android Runtime)。
在整个 Android 操作系统体系中,ART 位于下图黄色小方块位置:
不是说被删除就无用了,咱们毕竟做这一行的还是要简单的了解一下。
1.1 Dalvik 和 JVM 区别
-
1、Dalvik 基于寄存器,而 JVM 基于栈。
-
2、基于寄存器的虚拟机对于更大的程序来讲,在它们编译的时候,花费的时间更短。
-
3、JVM字节码中,局部变量会被放入局部变量表中,继而被压入堆栈供操做码进行运算,固然JVM也能够只使用堆栈而不显式地将局部变量存入变量表中。
-
4、Dalvik字节码中,局部变量会被赋给65536个可用的寄存器中的任何一个,Dalvik指令直接操做这些寄存器,而不是访问堆栈中的元素。
1.2 Dalvik 如何运行 java
-
VM字节码由.class文件组成,每一个文件一个class。
-
JVM在运行的时候为每个类装载字节码。相反的,Dalvik程序只包含一个.dex文件,这个文件包含了程序中全部的类。
-
Java编译器建立了JVM字节码以后,Dalvik的dx(d8)编译器删除.class文件,从新把它们编译成Dalvik字节码,而后把它们写进一个.dex文件中。这个过程包括翻译、重构、解释程序的基本元素(常量池、类定义、数据段)。
-
常量池描述了全部的常量,包括引用、方法名、数值常量等。类定义包括了访问标志、类名等基本信息。数据段中包含各类被VM执行的函数代码以及类和函数的相关信息(例如DVM所须要的寄存器数量、局部变量表、操做数堆栈大小),还有实例变量。
可参考:# JVM 内存模型
1.3 dex文件
class 文件是由一个 java 源码文件生成的 class 文件,而 Android 是把所有 class 文件进行合并优化,然后生成一个最终的 class.dex 文件。dex 文件去除了 class 文件中的冗余信息(比如重复字符常量),并且结构更加紧凑,因此在 dex 解析阶段,可以减少 I/O 操作,提高了类的查找速度。
实际上,dex 文件在 App 安装过程中还会被进一步优化为 odex(optimized dex)。
注意:这一优化过程也会伴随着一些副作用,最经典的就是 Android 65535 问题。
65535
Android 应用 APK 包含 DEX 可执行字节码文件,其中包含用于运行应用的编译代码。Dalvik Executable 规范将单个 DEX 文件中可以引用的方法总数限制为 65,536,包括 Android 框架方法、库方法和你自己代码中的方法。由于 65,536 等于 64 X 1024,因此此限制称为“64K 参考限制”。
2. Android Runtime (ART)
ART 是 Android 上的应用和部分系统服务使用的托管式运行时。ART 及其前身 Dalvik 最初是专为 Android 项目打造的。
ART 和 Dalvik 是运行 Dex 字节码的兼容运行时,因此针对 Dalvik 开发的应用也能在 ART 环境中运作。不过,Dalvik 采用的一些技术并不适用于 ART。
2.1 ART 的启动
在Zygote进程的 main() 方法中 创建 ART 并调用 ART.start() 方法启动ART。
可参考:应用是如何启动的
3. ART 编译功能
3.1 预先 (AOT) 编译
ART 引入了预先编译机制,可提高应用的性能。ART 还具有比 Dalvik 更严格的安装时验证。
在安装时,ART 使用设备自带的 dex2oat 工具来编译应用生成 .oat 文件。该工具应能够顺利编译所有有效的 DEX 文件。但是,一些后处理工具会生成无效文件,Dalvik 可以接受这些文件,但 ART 无法编译这些文件。
AOT:可以理解为一种编译策略,即运行前编译。应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。之后打开App的时候,直接使用本地机器码运行,因此运行速度提高。
-
AOT优点:
-
在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗
-
可以在程序运行初期就达到最高性能
-
可以显著的加快程序的启动
-
-
AOT缺点:
-
安装的时间增加(字节码就会预先编译成机器码)
-
字节码转换成机器码,因此消耗掉更多的存储空间(通常不超过应用代码包大小的20%)
-
牺牲Java的一致性
-
3.2 编译、解释和JIT(Just In Time)的混合运行
Android N引入了一种包含编译、解释和JIT(Just In Time)的混合运行时,以便在安装时间、内存占用、电池消耗和性能之间获得最好的折衷。
- 优点如下:
-
缩短应用安装时间过长
-
降低占用的储存空间
-
提升系统与应用性能
-
快速的系统升级
-
3.3 即时 (JIT) 编译
JIT 编译器对 Android 运行组件当前的预先 (AOT) 编译器进行了补充,可以提升运行时性能,节省存储空间,加快应用和系统更新速度。相较于 AOT 编译器,JIT 编译器的优势也更为明显,因为在应用自动更新期间或在无线下载 (OTA) 更新期间重新编译应用时,它不会拖慢系统速度。
3.3.1 JIT 架构
3.3.2 JIT 编译
-
运行应用,触发 ART 加载
.dex文件。- 有
.oat文件(即.dex文件的 AOT 二进制文件),ART 会直接使用该文件。虽然.oat文件会定期生成,但文件中不一定会包含经过编译的代码(即 AOT 二进制文件)。 - 如果
.oat文件不含经过编译的代码,ART 会通过 JIT 和解释器执行.dex文件。
- 有
-
针对任何未根据
speed编译过滤器编译的应用启用 JIT(也就是说,要尽可能多地编译应用中的代码)。 -
将 JIT 配置文件数据转储到只有该应用可以访问的系统目录下的文件中。
-
AOT 编译 (
dex2oat) 守护程序通过解析该文件来推进其编译。
关于JIT的更多内容可以去实现 ART 即时 (JIT) 编译器:
3.4 ART 的运作方式
例如我们的一款荣耀V10设备安装SC应用:
-
最初安装应用时不进行任何 AOT 编译。应用前几次运行时,系统会对其进行解译,并对经常执行的方法进行 JIT 编译。
-
当设备闲置和充电时,编译守护程序会运行,以便根据在应用前几次运行期间生成的配置文件对常用代码进行 AOT 编译。
-
下一次重新启动应用时将会使用配置文件引导型代码,并避免在运行时对已经过编译的方法进行 JIT 编译。在应用后续运行期间经过 JIT 编译的方法将会添加到配置文件中,然后编译守护程序将会对这些方法进行 AOT 编译。
3.4.1 编译选项
ART 的编译选项分为以下两个类别:
-
系统 ROM 配置:构建系统映像时,会对哪些代码进行 AOT 编译。
-
运行时配置:ART 如何在设备上编译和运行应用。
用于配置这两个类别的一个核心 ART 选项是"编译过滤器"。
有四个官方支持的过滤器:
-
verify:只运行 DEX 代码验证。
-
quicken:运行 DEX 代码验证,并优化一些 DEX 指令,以获得更好的解译器性能。
-
speed:运行 DEX 代码验证,并对所有方法进行 AOT 编译。
-
speed-profile:运行 DEX 代码验证,并对配置文件中列出的方法进行 AOT 编译。
4. 内存管理
4.1 ART GC 概述
垃圾回收 (GC) 会耗费大量资源,这可能有损于应用性能,产生卡顿、内存抖动(产生OOM)的等问题。
ART 有几个不同的 GC 计划,包括运行不同的垃圾收集器。从 Android 8 (Oreo) 开始,默认方案是并发复制 (CC)。另一个 GC 方案是并发标记扫描 (CMS)。
Concurrent Copying(并发复制) GC 的一些主要特征是:
- CC 支持使用称为 RegionTLAB 的凹凸指针分配器。这会为每个应用程序线程分配一个线程本地分配缓冲区 (TLAB),然后可以通过碰撞"top"指针从其 TLAB 中分配对象,无需任何同步。
- CC 通过并发复制对象而不暂停应用程序线程来执行堆碎片整理。这是在读取屏障的帮助下实现的,该屏障拦截来自堆的引用读取,无需应用程序开发人员的任何干预。
- GC 只有一个小的暂停,就堆大小而言,它在时间上是恒定的。
- CC 扩展为 Android 10 及更高版本中的分代 GC。它可以轻松收集通常很快变得无法访问的年轻对象。这有助于提高 GC 吞吐量并大大延迟执行全堆 GC 的需要。
ART 仍然支持的另一个 GC 是 CMS。此 GC 也支持压缩,但不能同时支持(应用程序线程会暂停以执行压缩),所以在应用程序进入后台之前避免压缩。
由于 CMS 很少压缩,因此空闲对象可能不连续,因此它使用基于空闲列表的分配器,称为 RosAlloc。与 RegionTLAB 相比,它具有更高的分配成本。最后,由于内部碎片,CMS 的 Java 堆内存使用率可能高于 CC。
在了解CC之前可以先了解复制算法(Copying)和CMS具体内容可参考:Java 垃圾回收(GC)
更深入一点可以研究一下老罗ART运行时垃圾收集(GC)过程分析
4.2 垃圾回收
ART 或 Dalvik 虚拟机之类的受管内存环境会跟踪每次内存分配。一旦确定程序不再使用某块内存,它就会将该内存重新释放到堆中,无需程序员进行任何干预。这种回收受管内存环境中的未使用内存的机制称为"垃圾回收"。垃圾回收有两个目标:
-
在程序中查找将来无法访问的数对象
-
回收这些对象使用的资源。
Android 的内存堆是分代的,这意味着它会根据分配对象的预期寿命和大小跟踪不同的分配存储分区。
可参考:Java 垃圾回收(GC)
4.3 共享内存
为了在 RAM 中容纳所需的一切,Android 会尝试跨进程共享 RAM 页面。它可以通过以下方式实现这一点:
- 每个应用进程都从一个名为 Zygote 的现有进程 fork。可参考:源码解读-应用是如何启动的
- 大多数静态数据会内存映射到一个进程中。这种方法使得数据不仅可以在进程之间共享,还可以在需要时换出。静态数据示例包括:Dalvik 代码、应用资源和 lib 中的文件。
- Android 使用明确分配的共享内存区域(通过 ashmem 或 gralloc)在进程间共享同一动态 RAM。例如,窗口 surface 使用在应用和屏幕合成器之间共享的内存,而光标缓冲区则使用在内容提供器和客户端之间共享的内存。
由于共享内存的广泛使用,在确定应用使用的内存量时需要小心谨慎。
4.4 限制应用内存
为了维持多任务环境的正常运行,Android 会为每个应用的堆大小设置硬性上限。不同设备的确切堆大小上限取决于设备的总体可用 RAM 大小。如果你的应用在达到堆容量上限后尝试分配更多内存,则可能会收到 OutOfMemoryError。
在某些情况下,你可以通过调用 getMemoryClass() 向系统查询此确切可用的堆空间大小。
应用程序使用多进程可以打破应用内存限制。
4.5 切换应用
当用户在应用之间切换时,Android 会将非前台应用(看不见或或未运行前台服务(如音乐播放))保留在缓存中。
5. 开发和调试
5.1 Android Profiler
- 使用 CPU Profiler 分析 CPU 活动和跟踪
- 使用 Memory Profiler 分析 Java 堆和内存分配
- 使用 Network Profiler 分析网络流量
- 使用 Energy Profiler 分析能源使用情况
5.1.1 CPU Profiler
点进去某个功能进入页面,后续可通过上方切换功能。
Traceview (用于跟踪应用执行情况)已弃用。
如果你使用的是 Android Studio 3.2 或更高版本,应使用 CPU Profiler 。
5.1.2 Memory Profiler
Memory Profiler 可帮助你识别可能导致卡顿、冻结甚至应用崩溃的内存泄漏和内存流失。它显示应用程序内存使用的实时图表,并让你捕获堆转储、强制垃圾回收和跟踪内存分配。
Network Profiler 和 Energy Profiler 用到可以自行查看,就不浪费大家时间了,文档还是比较简单的。
5.2 支持更多调试功能
ART 支持许多新的调试选项,特别是与监控和垃圾回收相关的功能。例如,你可以:
- 查看堆栈跟踪中保留了哪些锁,然后跳转到持有锁的线程。
- 询问指定类的当前活动的实例数、请求查看实例,以及查看使对象保持有效状态的参考。
- 过滤特定实例的事件(如断点)。
- 查看方法退出(使用“method-exit”事件)时返回的值。
- 设置字段观察点,以在访问和/或修改特定字段时暂停程序执行。
5.3 提供异常详细信息
当发生运行时异常时,ART 会为你提供尽可能多的上下文和详细信息。
- ART 会提供异常详细信息 :
- java.lang.ClassCastException
- java.lang.ClassNotFoundException
- java.lang.NullPointerException 。
- 高版本还是提供以下异常信息
- java.lang.ArrayIndexOutOfBoundsException
- java.lang.ArrayStoreException
ART 还通过纳入 Java 和原生堆栈信息,在应用原生代码崩溃报告中提供更实用的上下文信息。
关于应用程序异常捕获可参考:Android 程序崩溃之快速锁定
小结
本文只是简单的对ART做了一个简要的介绍,如果对虚拟机感兴趣的可以加深对JVM和ART的了解,DVM只是Android 5.0以前使用的,现在已经用不到了,更多内容后面再议。