虚拟机
Android的虚拟机主要有两种:Dalvik 虚拟机
和 ART(Android Runtime)虚拟机
。
Dalvik 虚拟机
Dalvik 虚拟机是 Android 早期使用的虚拟机,它基于寄存器架构
。从Android 2.2版本开始,支持JIT即时编译(Just In Time)
在程序运行的过程中进行选择热点代码(经常执行的代码)进行编译或者优化。Dalvik 执行的是经过转换优化后的 dex(Dalvik Executable)字节码文件,而不是传统的 Java 字节码。Dalvik 虚拟机针对移动设备的特点进行了优化,具有较小的内存占用和较高的执行效率。每个应用程序都在独立的 Dalvik 虚拟机实例中运行,实现了应用程序之间的隔离。
ART 虚拟机
自 Android 5.0(Lollipop)起,Android 引入了 ART 虚拟机作为默认的运行环境。ART 使用 Ahead-Of-Time(AOT)编译技术,将 dex 字节码在应用安装时预先编译成本地机器码,这样在运行时就无需再进行实时的字节码转换,提高了应用程序的启动速度和执行效率。与 Dalvik 不同,ART 的执行方式更接近于传统的 Java 虚拟机,采用基于堆栈的指令集
。
区别
基于寄存器的虚拟机
在基于寄存器的虚拟机中,将数据保存在寄存器
中。虚拟寄存器可以看作是一个数组
,用于存储局部变量、参数和临时变量等运行时数据。每个虚拟寄存器都有自己的编号,可以通过编号来访问和操作寄存器中的值。
在 Dalvik VM 中,每个线程都有自己的程序计数器
(Program Counter,PC)和调用栈
。程序计数器用于记录当前线程执行到的指令位置,而调用栈则用于保存方法调用的活动记录,每个活动记录称为一个帧(frame)。每个帧包含了方法的局部变量表和操作数寄存器等信息。
因此,对于基于寄存器的虚拟机来说,数据存放在寄存器中,虚拟寄存器是一个数组,保存在运行时栈中。每个线程都有自己的程序计数器和调用栈,方法调用的活动记录以帧为单位保存在调用栈上。
基于栈的虚拟机
每个虚拟机运行时线程都有自己独立的栈
。栈用来记录方法调用的历史,每当一个方法被调用
时,就会在栈中创建一个新的栈帧(stack frame)
。栈帧包含了方法的局部变量表、操作数栈以及一些额外的信息。
栈的顶部栈帧被称为当前栈帧
,代表当前正在执行的方法
。栈帧中的局部变量表用于存储方法中定义的局部变量和参数,而操作数栈用于执行方法中的操作。
基于栈的虚拟机通过操作数栈
进行所有的指令操作。指令可以从操作数栈中取出操作数,执行相应的操作,然后将结果再次压入操作数栈中。这种基于栈的指令集设计简洁紧凑,并且不依赖于具体的硬件架构,因此能够实现较好的跨平台性。
当方法执行完成
或者遇到方法调用时,当前栈帧会被弹出
,并且恢复到上一个栈帧
,继续执行上一个方法。这样,通过不断创建和销毁栈帧,基于栈的虚拟机能够实现方法之间的无缝切换和控制流的管理。
总结起来,基于栈的虚拟机使用独立的栈来记录方法调用历史,每个栈帧代表一个方法的执行上下文,包含局部变量表和操作数栈。通过操作数栈进行指令操作,实现方法间的切换和控制流管理。这种设计具有简洁、紧凑和跨平台等特点。
Android程序安装优化
当一个应用在 Android 系统上被安装时,会根据所使用的运行时环境(Dalvik 或 ART)执行不同的优化过程。
在 Dalvik 虚拟机
中,应用在安装过程中会进行一次优化,将应用的 dex 字节码
转换为优化的可执行文件 odex(Optimized DEX)文件
。这个优化过程由 dexopt
工具完成。dexopt 根据设备配置和系统策略,对 dex 文件进行字节码优化、预解析和预验证等操作,并生成 odex 文件。odex 文件包含了已经优化过的字节码以及相关的元数据信息,加速了应用运行时的加载和执行速度。
而在 ART(Android Runtime)
中,引入了预先编译机制(Ahead Of Time Compilation)
。在应用安装时,ART 使用设备自带的dex2oat
工具将应用的 dex 字节码直接编译成本地机器码,也就是将应用的 dex 文件转换为 ELF(Executable and Linkable Format)可执行文件
。这个编译过程可以发生在应用安装期间
,也可以在应用首次运行时发生,具体取决于设备和系统策略。
通过将 dex 字节码编译为本地机器码,ART 提供了更高的应用执行性能和更低的内存占用。与 Dalvik 不同,ART 在应用运行时无需进行即时编译(Just-In-Time Compilation),而是直接执行本地机器码
,提高了应用的响应速度和效率。