一、Dalvik 虚拟机
Dalvik是Google公司自己设计用于Android平台的Java虚拟机,它是Android平台的重要组成部分,支持dex格式的Java应用程序的运行。
Dalvik作为面向Linux、为嵌入式操作系统设计的虚拟机,主要负责完成 对象生命周期管理、堆栈管理、线程管理、安全和异常管理,以及垃圾回收等。Dalvik充分利用Linux进程管理的特定,对其进行了面向对象的设计,使得可以 同时运行多个进程,而传统的Java程序通常只能运行一个进程。
1.1 Dalvik 虚拟机和 JVM 的对比
区别一
- 大多数的
JVM虚拟机基于 栈的结构,基于栈的指令更紧凑,使用的指令只占用一个字节,因而成为字节码。 - 而
Dalvik虚拟机则是基于 寄存器,基于寄存器的指令由于需要指定源地址和目标地址,因此需要占用更多的指令空间,某些指令需要占用两个字节。
区别二
-
Java虚拟机运行的是Java字节码。Java类会被编译成一个或者多个.class文件,然后打包到jar文件中,接着Java虚拟机会从相应的.class文件和.jar文件中获取对应的字节码。 -
Dalvik虚拟机运行的是.dex文件。在Java类被编译成.class文件后,还会通过dx工具将所有的.class文件转换一个.dex文件,Dalvik虚拟机再从中读取指令和数据。.dex文件除了减少整体的文件尺寸和I/O操作次数,也提高了类的查找速度。
区别三
-
class文件中包含多个不同的方法签名,如果A类文件引用B类文件中的方法,方法签名也会被复制到A类文件中(在虚拟机加载类的连接阶段将会使用该签名链接到B类的对应方法),也就是说,多个不同的类会同时包含相同的方法签名,同样地,大量的字符串常量在多个类文件中也被重复使用,这些冗余信息会直接增加文件的体积,而JVM在把描述类的数据从.class文件加载到内存时,需要对数据进行校验、转换解析和初始化,最终才形成可以被虚拟机直接使用的JAVA类型,因为大量的冗余信息,会严重影响虚拟机解析文件的效率。 - 在
.dex文件中,由于dx工具会对JAVA类文件重新排列,将所有JAVA类文件中的常量池分解,消除其中的冗余信息,重新组合形成一个常量池,所有的类文件共享同一个常量池,使得相同的字符串、常量在.dex文件中只出现一次,从而减小了文件的体积。
1.2 Dalvik 虚拟机特点
- 使用
dex格式的字节码,不兼容Java字节码格式 - 代码密度小,运行效率高,节省资源
- 常量池只使用
32位的索引 - 有内存限制
- 默认栈大小是
12KB - 堆默认启动大小为
2MB,默认最大值为16MB - 堆支持的最小启动大小为
1MB,支持的最大值为1024MB - 堆和栈参数可以通过
-Xms和-Xmx修改
1.3 Dalvik 系统架构
1.3.1 dex 文件结构

.dex文件结构和.class文件结构差异的地方很多,但从携带的信息上看,.dex和.class文件是一致的:
-
header:存储了各个数据类型的起始地址、偏移量等信息。 -
proto_ids:描述函数原型信息,包括返回值,参数信息。比如“test:()V” -
methods_ids:函数信息,包括所属类及对应的proto信息。
虽然.dex文件的结构很紧凑,但想要运行时的性能得到进一步提升,还需要对dex文件进行进一步优化。优化主要针对以下几个方面:
- 调整所有字段的字节序和对齐结构中的每一个域
- 验证
.dex文件中的所有类 - 对一些特定的类进行优化,对方法里的操作码进行优化
.dex文件经过优化后文件大小会膨胀,大约增加到原来的1~4倍。对于内置应用,一般在系统编译后,便会生成优化文件odex(Optimized dex)。一个Android应用程序,需要经过以下过程才可以在Dalvik虚拟机上运行:
- 把
Java源文件编译成.class文件 - 使用
dx工具把.class文件转换成.dex文件 - 使用
aapt工具把.dex文件、资源文件以及AndroidManifest.xml文件组合成APK - 将
APK安装到Android设备运行

1.3.2 Dalvik 类加载器
一个dex文件需要类加载器加载原生类和Java类,然后通过解释器根据指令集对Dalvik字节码进行解释和执行。Dalvik类加载器使用mmap函数,将dex文件映射到内存中,通过普通的内存读取操作即可访问dex文件,然后解析dex文件内容并加载其中的类到哈希表中。
解析 dex
总的来说,dex文件可以抽象为三个部分:头部、索引、数据。通过头部可以知道索引的位置和数目,以及数据区的起始位置。将dex文件映射到内存后,Dalvik会调用dexFileParse函数对其进行分析,分析的结果放到DexFile数据结构中。DexFile中的baseAddr指向映射区的起始位置,pClassDefs指向class索引的起始位置。为了加快class的查找速度,还创建一个哈希表,对class名字进行哈希并生成索引。
加载 class
解析工作完成后就进行class的加载,加载的类需要用ClassObject数据结构来存储。
typedef struct Object {
ClassObject* clazz; // 类型对象
Lock lock; // 锁对象
} Object;
其中clazz指向ClassObject对象,还包含一个Lock对象。如果其它线程想要获取它的锁,只有等这个线程释放。Dalvik每加载一个class都会对应一个ClassObject对象,加载过程会在内存中分配几个区域,分别存放directMethod、virtualMethod、sfield、ifield。这些信息从dex文件的数据区中读取。字段Field的定义如下:
struct Field {
ClassObject* clazz; //所属类型
const char* name; // 变量名称
const char* signature; // 如“Landroid/os/Debug;”
u4 accessFlags; // 访问标记
#ifdef PROFILE_FIELD_ACCESS
u4 gets;
u4 puts;
#endif
};
待得到class索引后,实际的加载由loadClassFromDex来完成。首先它会读取class的具体数据,分别加载directMethod、virtualMethod、ifield和sfield,然后为ClassObject数据结构分配内存,并读取dex文件的相关信息。加载完成后,将加载的class通过dvmAddClassToHash函数放入哈希表,以方便下次查找;最后,通过dvmLinkClass查找该类的超类,如果有接口类则加载相应的接口类。
1.3.3 Dalvik 解释器
对于任何虚拟机来说,解释器无疑是核心的部分,所有的Java字节码都经过解释器解释执行。由于Dalvik解释器的效率很重要,Android分别实现了C语言版和各种汇编语言版的解释器。解释器通常是循环执行,需要一个入口函数调用处理程序执行第一条指令,而后每条指令执行时引出下一条指令,通过函数指针调用处理程序。
二、Dalvik 虚拟机和 ART 虚拟机对比
在Android 4.4之后,Google开始使用了更加优秀的ART虚拟机来替换Dalvik虚拟机,下面我们就来对比一下这两者之间的区别。
2.1 Dalvik
在 打包的过程中 会先将.java等源码通过javac编译成.class文件,再通过dx将.class文件转换成Dalvik虚拟机执行的.dex文件。
在 应用启动的时候 先将.dex文件 转换成机器码,又因为65536的文件,导致在应用冷启动的时候有一个合包的过程,最后的结果就是app的启动时间有可能变慢,这就是Dalvik虚拟机的JIT(Just in Time)特性。
2.2 ART
ART除了兼容了Dalvik虚拟机的特性之外,还有一个很好的特性AOT(Ahead of Time),这个特性就是把 .dex 文件转换成机器码 这个步骤提前到了 应用安装 的时候,ART虚拟机将.dex文件转换成可直接运行的.oat文件,ART虚拟机天生支持多dex,所以也不会有一个合包的过程,因此会极大的提升APP冷启动速度。
2.3 ART 虚拟机的优缺点
优点:
- 加快
APP冷启动速度 - 提升
GC速度 - 提供功能全面的
Debug特性
缺点:
-
APP安装速度慢,因为在APK安装的时候要生成可运行.oat文件 -
APK占用空间大,因为在APK安装的时候要生成可运行.oat文件
三、参考文献
理解 Android 虚拟机体系结构
Android Dalvik 虚拟机和 ART 虚拟机对比
Dalvik 虚拟机简要介绍和学习计划
Dalvik 虚拟机的启动过程分析
Dalvik 虚拟机的运行过程分析
深入理解 Dalvik 虚拟机 - Android应用进程启动过程分析
深入理解 ART 虚拟机 - 虚拟机的启动
深入理解 ART 虚拟机 - ART 的函数运行机制
深入理解 Dalvik 虚拟机 - 解释器的运行机制
深入理解ART虚拟机 - ImageSpace的加载过程分析
更多文章,欢迎访问我的 Android 知识梳理系列:
- Android 知识梳理目录:http://www.jianshu.com/p/fd82d18994ce
- Android 面试文档分享:http://www.jianshu.com/p/8456fe6b27c4