概述
Android开发虽然基于Java,但是有自己的虚拟机,Android应用程序运行在ART/Dalvik虚拟机上,并非Java虚拟机。Android虚拟机其实也算是Java虚拟机,两者大部分特性是相同的,主要不同在于执行文件和执行指令集。
Android虚拟机 | Java虚拟机 | |
执行文件格式 | .dex文件 | .class文件 |
指令集 | 基于寄存器 | 基于堆栈 |
- Java中xxx.java文件编译后生成.class文件。
- Android中xxx.java代码编译后生成.dex文件,打包后再解压可以看到有很多.dex文件。
- 一个.dex文件包含很多class,而一个.class文件中只有一个class。
- 每一个Android应用程序都对应一个单独的Android虚拟机实例。
虚拟机
虚拟机位于程序与硬件之间,将程序翻译成各种CPU可以执行的指令,而不用关心CPU架构,实现一次编译,到处运行的效果。
Java虚拟机
Java虚拟机是基于栈的,即每一个运行的线程,都有一个独立的栈,每调用一个方法,栈中就会多一个栈帧,最顶部的栈帧就是当前正在执行的方法,而虚拟机通过操作数栈对栈帧进行操作,所以栈就是方法调用的记录。
通过Java中编译生成的字节码Bytecode,可以看到Java程序是怎样一步一步的执行,详细过程可以参考Java随笔-方法执行与栈帧。
总得来讲就是程序计数器记录程序执行位置,操作数栈不断的压栈、出栈,局部变量表不停的存值、取值,操作数栈再不断的压栈、出栈,执行计算…。
Android虚拟机
Dalvik
Android虚拟机最开始默认使用的是Dalvik虚拟机执行.dex文件,从Android2.2开始支持即时编译JIT(Just In Time),在程序运行的过程中选择热点代码(经常执行的代码)进行编译或优化。Dalvik和JVM很相似,Android5.0后被废弃。
与JIT一起的还有Interpreter(解释执行),就是一边把字节码翻译成当前硬件平台的机器码一边执行。启动比较快,就是执行效率低下。
ART
Android4.4开始在开发中选项中引入了ART(Android Runtime),在Android5.0后就完全使用ART。ART执行的是本地机器码,所以执行效率要比Dalvik快很多,能明显减少卡顿现象,这也是为什么废弃Dalvik使用ART的原因。
ART 采用了一种叫AOT (ahead of time) 来代替目前的在 runtime 时的 Interpreter 与 JIT。ART 不是等到App运行的时候才去运行dex文件,而是在App安装的时候就通过 AOT编译器 将.dex文件通过dex2oat编译为对应的.oat二进制文件,dex中的字节码会被编译成本地机器码。当用户点击App的启动图标时,ART直接加载.oat文件去执行。其中那个.oat文件就是一个ELF文件,其是当前机器的可执行的文件。所以ART在安装应用的时候要比Dalvik慢一点。
- dexopt
使用Dalvik加载dex文件时,会对dex文件进行验证和优化,dex被优化后就成了odex文件。 - dex2oat
ART在安装时对dex文件执行提前编译操作,编译为OAT文件,实际上也是ELF机器码。
混合编译
从Android N开始,Android开始混合使用AOT编译,解释执行,JIT。执行步骤如下:
- 首次安装APP时,不使用AOT编译,减少了安装时间。系统会通过Interpreter解释执行的方式启动APP,提升了启动速度。
- APP运行过程中热点代码使用JIT编译。
- 使用JIT编译的方法会被记录在配置文件Profile中。
- 当设备充电或处于空闲的时候,编译守护线程会对Profile中记录的方法进行AOT编译,将dex文件编译成oat文件。
- 再次运行APP时,直接执行oat文件。
比较
Android虚拟机基于Java虚拟机,所以先说Java虚拟机。Android虚拟机基于寄存器,没有操作数栈,少了很多出入栈的操作。
Android虚拟机与JVM想比程序指令明显减少,数据移动数据次数也明显减少。
栈式VS寄存器式 | |
指令条数 | 栈式 > 寄存器式 |
代码尺寸 | 栈式 < 寄存器式 |
移植性 | 栈式 > 寄存器式 |
指令优化 | 栈式 < 寄存器式 |
解释器执行速度 | 栈式 < 寄存器式 |
代码生成难度 | 栈式 < 寄存器式 |
数据移动次数 | 栈式 > 寄存器式 |