概述
安卓的四层架构,最上面两层,(应用层,应用框架层)都是java编写的,剩余三层是 库和运行时,硬件层,linux内核层 是C++写的。安卓中我们接触最多的还是 java代码(kotlin只是java的高级变种,最终也是转化成java代码统一处理)。
所以,安卓代码实际上也是运行在JVM上,只不过 GOOGLE针对 JVM进行了专项优化,使之更加适合在移动平台上运行。
注意:在安卓5.0之前,优化之后的JVM,被谷歌叫做 DVM (dalvik Virtual Machine),5.0以及以后,都称之为 ART (android Runtime)
ART的位置,在 应用框架层再往下。处在库和运行时
这一层
优化细节
由于ART是要运行在移动端,手机,平板,对于内存控制的要求就更高,毕竟手机平台的内存不可能比得上大型PC。 而且ART的前身DVM当初设计出来只是为了适配 ARM架构的CPU。
所以,针对更严格的内存要求,以及特定的ARM CPU架构,对JVM做出自己的优化措施。
Dex VS class
传统Class文件是由一个java文件通过javac命令编译而成, android则是把所有的class文件进行优化合并,然后生成一个最终的classes.dex文件,dex文件去掉了 class文件中的冗余信息,使之结构更加紧凑,所以在dex解析阶段,可以减少I/O操作,提高类查找速度。
理解:dex是合并了所有class之后的产物。回顾一下,class文件结构中,存在一个常量池,是保存了某一个class里面的常量集合,那么多个class中是否可能存在多个相同的常量呢? 很有可能,比如两个 class都定义了相同的常量字符串,那么dex将他们整理到一起,集中放在一起,再安排带缓存的读取机制,那么在访问多个类的时候便能减少IO次数了。
比如下方两个java类:
通过javac和jar命令生成一个jar包:
然后用dx命令将他们生成 dex文件:
最后通过dexdump查看dx文件的内容:
如下:
可以看到,两个类的信息,都在同一个dex中。
问题
将多个class集中到一个dex中,能达到节约空间,资源整合,减少I/O的优点,但是同样带来了一些问题,比如 经典的65535问题。当一个dex中的方法数超过65535,就会导致无法打包。
注意:很多网上的说法是,65535问题是由于 解析dex文件时使用了short来存储 方法个数,这种说法是错误的。
正确的解释应该是:
DVM的源代码中,在将多个class编排到dex中时,直接对方法数 ,成员变量数,都进行了最大数量的限制,超出数量就抛出异常。
指令基于寄存器 VS 指令基于栈
JVM的指令集的执行,是基于栈结构去执行的,而 android 是基于寄存器。区别如下:
如下一个add方法,
在 JVM中执行的指令集如下:
有4个步骤,2个变量分别入栈,对两个变量进行add操作,以及最终的return。
而 安卓执行这一个方法的 指令集为:
原来的4个步骤,浓缩成了2个。
ART是基于寄存器,add-int这个指令 会将 v2和v3相加运算,最终将结果保存在寄存器 v0中。
安卓的ART指令执行的过程,相当于原来JVM的执行过程的优势为:
- 速度更快
- 指令条数更少
而相应地做出了牺牲:
- 指令更长
- 指令更复杂
- 可移植性更差
堆内存的管理优化
ART将堆分为了两个部分,分别是
- 共有 Zygote Heap
- 私有的 Activte Heap
所有的app进程都和zygote进程共享同一个zygoteHeap区域。
ART将堆内存分成两块的原因是:每一个安卓app进程都是由 Zygote进程fork而来。当Zygote进程在fork第一个app进程时,会将已经使用的堆内存划分为 Zygote Heap ,将没有使用的那部分内存划分为 Active Heap,当需要分配对象时,都在 ActiveHeap中进行。这样可以使得ZygoteHeap尽可能少的执行写操作。可以减少拷贝的时间。
堆 heap,其实就是一块匿名共享内存,安卓系统为了解决内存碎片的问题,将内存的分配和管理,交给了 底层C库中的malloc函数。