Dalvik 和 ART
Dalvik 虚拟机
Dalvik 虚拟机(Dalvik Virtual Machine),简称 Dalvik VM 或者 DVM。它是由 Dan Bornstein 编写的,名字源于他的祖先居住过的名为 Dalvik 的小渔村。DVM 是 Google 专门为 Android 平台开发的虚拟机,它运行在 Android 运行时库中。
DVM 与 JVM 的区别
DVM 并不是一个 JVM 虚拟机,主要原因是 DVM 并没有遵循 JVM 规范来实现。主要区别如下:
- 基于的架构不同。JVM 基于栈,而 DVM 基于寄存器。
详细信息
JVM 基于栈则意味着需要去栈中读写数据,所需指令会更多,这样会导致速度变慢,显然不适合性能有限的移动设备。DVM 是基于寄存器的,它没有基于栈的虚拟机在复制数据时而使用的大量的出入栈指令,同时指令更紧凑、更简洁。但是由于显式指定了操作数,所以基于寄存器的指令会比基于栈的指令要大,但是由于指令数量的减少,总的代码数不会增加多少。
执行的字节码不同。JVM 执行的是 .jar 文件,而 DVM 执行的是 .dex 文件。
详细信息
在 Java SE 程序中,Java 类被编译成一个或多个 .class 文件,并打包成 jar 文件,而后 JVM 会通过相应的 .class 文件和 jar 文件获取相应的字节码。执行顺序为 .java 文件 -> .class 文件 -> .jar 文件,而 DVM 会用 dx 工具将所有的 .class 文件转换为一个 .dex 文件,然后 DVM 会从该 .dex 文件读取指令和数据。执行顺序为 .java 文件 -> .class 文件 -> .dex 文件。JVM 加载 .jar 文件时,会加载里面所有的 .class 文件,每个 .class 文件里面包含了该类的常量池、类信息、属性等,对于内存有限的移动设备并不适合这种慢的加载方式。而在 .apk 文件中只包含了一个将所有 .class 里面所包含的信息全部整合在一起的 .dex 文件,dex 工具还会除去 .class 文件存在的很多的冗余信息,减少了 I/O 操作,加快了类的查找速度,提升了加载速度。
DVM 允许在有限的内存中同时运行多个进程。
详细信息
在 Android 中每一个应用都运行在一个 DVM 实例中,每一个 DVM 实例都运行在一个独立的进程空间中,独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。
DVM 由 Zygote 创建和初始化。
详细信息
Zygote 它是一个 DVM 进程,同时也用来创建和初始化 DVM 实例。每当系统需要创建一个应用程序时,Zygote 就会 fock 自身,快速地创建和初始化一个 DVM 实例,用于应用程序的运行。对于一些只读的系统程序,所有的 DVM 实例都会和 Zygote 共享一块内存区域,节省了内存开销。
DVM 有共享机制。
详细信息
DVM 拥有预加载—共享的机制,不同应用之间在运行时可以共享相同的类,拥有更高的效率。而 JVM 机制不存在这种共享机制,不同的程序,打包以后的程序都是彼此独立的,即使它们在包里使用了同样的类,运行时也都是单独加载和运行的无法进行共享。
DVM 早期没有使用 JIT 编译器。
详细信息
JVM 使用了 JIT 编译器(即时编译器),而 DVM 早起没有使用。早起的 DVM 每次执行代码,都需要通过解释器将 dex 代码编译成机器码,然后交给系统处理,效率不是很高。所以从 Android 2.2 版本开始 DVM 使用了 JIT 编译器,它会对多次运行的代码(热点代码)进行编译,生成相当精简的本地机器码(Native Code),这样在下次执行到相同逻辑时,直接使用编译之后的本地机器码,而不是每次都需要编译。需要注意的是,应用程序每一次重新运行时,都要重做这个编译工作,因此每次重新打开应用程序,都需要 JIT 编译。
DVM 架构
首先 Java 编译器编译的 .class 文件经过 DX 工具转换为 .dex 文件,.dex 文件由类加载器处理,接着解释器根据指令集对 Dalvik 字节码进行解释、执行,最后交于 Linux 处理。
DVM 的运行时堆
它使用标记—清除(Mark-Sweep)算法进行 GC,由两个 Space 以及多个辅助数据结构组成。
两个数据结构分别是:Zygote Space(Zygote Heap)和 Allocation Space(Active Heap)。
详细信息
Zygote Space 用来管理 Zygote 进程在启动过程中预加载和创建的各种对象,Zygote Space 中不会触发 GC,在 Zygote 进程和应用程序进程之间会共享 Zygote Space。在 Zygote 进程 fork 第一个子进程之前,会把 Zygote Space 分为两部分,原来的已经被使用的那部分堆仍旧叫 Zygote Space,而未使用的那部分堆就叫做 Allocation Space,以后的对象都会在 Allocation 上进行分配和释放。Allocation Space 不是进程间共享的,在每个进程中独立拥有一份。
包含的数据结构:Card Table、Heap Bitmap、Mark Stack。
详细信息
Card Table:用于 DVM Concurrent GC,当第一次进行垃圾标记后,记录垃圾信息。
Heap Bitmap:有两个 Heap Bitmap,一个用来记录上次 GC 存活的对象,另一个用来记录这次 GC 存活的对象。
Mark Stack:DVM 的运行时堆使用标记一清除 (Mark-Sweep) 算法进行 GC,Mark Stack 就是在 GC 的标记阶段使用的,它用来遍历存活的对象。
DVM 的 GC 日志
在 DVM 中每次垃圾收集都会将 GC 日志打印到 logcat 中,具体的格式为:
D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>
ART 虚拟机
ART(Android Runtime)虚拟机是 Android 4.4 发布的,用来替换 Dalvik 虚拟机,Android 4.4 默认采用的还是 DVM,系统会提供一个选项来开启 ART。在 Android 5.0 版本中默认采用了 ART,DVM 从此退出历史舞台。
ART 与 DVM 的区别
ART 中,系统在安装应用程序时会进行一次 AOT,并在 Android 7.0 版本加入了 JIT。
详细信息
DVM 中的应用每次运行时,字节码都需要通过 JIT 编译器编译为机器码,这会使得应用程序的运行效率降低。而 ART 中,系统会在安装应用程序时进行一次 AOT(预编译),将字节码预先编译为机器码并存储在本地,这样应用程序每次运行时就不需要执行编译了,运行效率会大大提升,设备的耗电量也会降低。采用 AOT 主要有两个缺点:一是应用程序安装时间变长,而是预先编译为机器码,机器码需要的存储空间会多一些,所以 Android 7.0 版本中的 ART 加入了即时编译器 JIT,在应用程序安装时并不会将字节码全部编译成机器码,而是在运行中将热点代码编译成机器码,从而缩短应用程序的安装时间并节省了存储空间。
DVM 是为 32 位 CPU 设计的,而 ART 支持 64 位并兼容 32 位 CPU,这也是 DVM 被淘汰的主要原因之一。
ART 对垃圾回收机制进行了改进,比如更频繁地执行并行垃圾收集,将 GC 暂停由 2 次减少为 1 次等。
ART 的运行时堆空间划分和 DVM 不同。
ART 的运行时堆
ART 采用了多种垃圾收集方案,每个方案会运行不同的垃圾收集器,默认采用 CMS(Concurrent Mark-Sweep)方案,该方案主要使用了 sticky-CMS 和 partial-CMS。
ART 的 GC 日志
ART 会为那些主动请求的垃圾收集事件或者认为 GC 速度慢(GC 暂停超过 5 ms 或者 GC 持续时间超过 100ms)时才会打印 GC 日志。如果 App 未处于可察觉的暂停进程状态,那么它的 GC 不会被认为是慢速的。
DVM 和 ART 的诞生
在 Zygote 进程中诞生的。
备注
参考资料:
Android 进阶解密