理解虚拟机(Android 虚拟机进化史)

版权声明:本文为CSDN博主「怪伽先森」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011033906/article/details/117806349

梳理一下各个 Android 版本的 虚拟机和编译策略

1. Dalvik or ART?
Android 4.4 以前用的是 Dalvik 虚拟机,Android 4.4 开始引入 ART 虚拟机。

Android 4.4 版本上两种运行时环境共存,可以相互切换。

Android 5.0 之后,Dalvik 虚拟机被彻底丢弃,全部采用 ART。

2. Android 诞生之初 ——> 单纯的 Dalvik
虽然Android 平台使用 Java 语言来开发应用程序,但Android程序却不是运行在标准Java虚拟机上的。可能是为了解决移动设备上软件运行效率的问题,也可能是为了规避与Oracle公司的版权纠纷。Google为Android平台专门设计了一套虚拟机来运行Android程序,它就是 Dalvik Virtual Machine(Dalvik虚拟机)。

Dalvik 负责加载 dex/odex 文件并解析成机器码交由系统调用。
2.1 Dalvik虚拟机概述
请先阅读 --进入Android Dalvik虚拟机
官方文档 – Android Runtime (ART) 和 Dalvik
Google于2007年底正式发布了Android SDK,Dalvik虚拟机也第一次进入了人们的视野。它的作者是丹·伯恩斯坦(Dan Bornstein),名字来源于他的祖先曾经居住过的名叫Dalvik的小渔村。Dalvik虚拟机作为Android平台的核心组件,拥有如下几个特点:
体积小,占用内存空间小;
专有的DEX可执行文件格式,体积更小,执行速度更快;
常量池采用32位索引值,寻址类方法名、字段名、常量更快;
基于寄存器架构,并拥有一套完整的指令系统;
提供了对象生命周期管理、堆栈管理、线程管理、安全和异常管理以及垃圾回收等重要功能;
所有的Android程序都运行在Android系统进程里,每个进程对应着一个Dalvik虚拟机实例。

2.2.1 基于寄存器架构的优点
JVM 基于栈架构。程序在运行时虚拟机需要频繁的从栈上读取或写入数据,这个过程需要更多的指令分派与内存访问次数,会耗费不少CPU时间,对于像手机设备资源有限的设备来说,这是相当大的一笔开销。

Dalvik虚拟机基于寄存器架构。数据的访问通过寄存器间直接传递,这样的访问方式比基于栈方式要快很多。

Dalvik设计之初是为了运行在嵌入式设备上,对性能要求比较高,并且对跨平台没有多大要求,因此Dalvik使用寄存器来加快代码的执行速度。

2.2.2 为什么使用 .dex 文件
Android SDK中有一个叫 dx 的工具负责将 Java 字节码转换为 Dalvik 字节码 --> .dex。

Android使用 Dex 文件来代替 Java 虚拟机的 class 文件,相比于 class 文件,Dex 文件有以下的改进:

Dalvik可执行文件体积更小
Dex 文件格式是专为 Dalvik 设计的一种压缩格式。所以可以简单的理解为:Dex 文件是很多 .class 文件处理后的产物,最终可以在 Android 运行时环境执行
由于dx工具对常量池的压缩,使得相同的字符串、常量在DEX文件中只出现一次,从而减小了文件的体积
由于生成的代码指令减少了,程序执行速度会更快一些
Dex文件的签名只有一份,验证也只有一次
dex文件有个天大的好处:可以直接用DexClassLoader类加载,这叫动态加载。于是我们只要在dex上加壳,在程序运行时脱壳,就可以规避静态反编译的风险

2.3 为什么用 Dalvik 而不是传统的 JVM?
Google为什么不用 JVM 来当做 android 虚拟机?原因是版权和效率问题:

为了解决移动设备上软件运行效率的问题
也可能是为了规避与Oracle公司的版权纠纷
2.3.1 Dalvik 相对于 JVM 在 Android 上的优势
Dalvik 运行 .dex 文件,文件体积更小,执行速度更快
Dalvik虚拟机基于寄存器架构
由于生成的代码指令减少了,程序执行速度会更快一些

3. Android 2.2 ——> JIT 首次登场
为了适应硬件速度的提升,Android 系统系统也在不断更新,单一的 Dalvik 虚拟机已经渐渐地满足系统的要求了,2010 年 5 月 20 日,Google 发布 Android 2.2(Froyo冻酸奶),在这个版本中,Google 在 Android 虚拟中加入了 JIT 编译器:Just-In-Time Compiler

Dalvik 虚拟机可以看做是一个 Java VM,他负责解释dex文件为机器码,如果我们不做处理的话,每次执行代码,都需要Dalvik将dex代码翻译为微处理器指令,然后交给系统处理,这样效率不高。为了解决这个问题,Google在2.2版本添加了JIT编译器,当App运行时,每当遇到一个新类,JIT编译器就会对这个类进行编译,经过编译后的代码,会被优化成相当精简的原生型指令码(即native code),这样在下次执行到相同逻辑的时候,速度就会更快。

JIT(Just-in-time Compilation,即时编译),又称为动态编译,是一种通过在运行时将字节码翻译为机器码的技术,使得程序的执行速度更快

官方宣称 JIT 的引入使得 Dalvik 的性能提升了 3~6 倍。

3.0 JIT 编译方式
主流的JIT包含两种字节码编译方式:

method方式:以函数或方法为单位进行编译。
trace方式:以trace为单位进行编译。
那什么是 trace 方式呢?在函数中一般很少是顺序执行代码的,多数的代码都分成了好几条执行路径,其中函数的有些路径在实际运行过程中是很少被执行的,这部分路径被称为“冷路径”,而执行比较频繁的路径被称为“热路径”。采用传统的 method 方式会编译整个方法的代码,这会使得在“冷路径”上浪费很多编译时间,并且耗费更多的内存;

trace方式编译则能够快速地获取“热路径”代码,使用更短的时间与更少的内存来编译代码。

目前,Dalvik虚拟机默认采用trace方式编译代码

3.1 JIT 优点
安装速度超快 存储空间小
每次应用在运行时,它实时的将一部分 dex翻译成机器码。在程序的执行过程中,更多的代码被被编译并缓存。由于 JIT 只翻译一部分代码,它消耗的更少的内存,占用的更少的物理存储空间
3.2 JIT 的缺点
但是 JIT 模式的缺点也不容忽视:

运行时比较耗电,造成电池额外的开销
JIT中需要解释器,解释器解释的字节码会带来CPU和时间的消耗 由于热点代码的Monitor一直在运行,也会带来电量的损耗
Android SDK < 21, 安装或者升级更新之后,首次冷启动的耗时漫长
Multidex加载的时候会非常慢,因为Dalvik 虚拟机只能执行做过 OPT 优化的 DEX 文件,也就是我们常说的 ODEX 文件
由于在Dex加载时会触发dexopt , 导致Multidex加载的时候会非常慢

3.3 Dalvik虚拟机是如何执行程序的
Android系统的架构采用分层思想,这样的好处是拥有减少各层之间的依赖性、便于独立分发、容易收敛问题和错误等优点。

Android系统由Linux内核、Libraries、Android Runtime、应用程序框架以及应用程序组成。

Dalvik虚拟机属于Android运行时环境,它与一些核心库共同承担Android应用程序的运行工作

3.3.1 Android 进程如何创建出来的
Android系统启动加载完内核后,第一个执行的是init进程,init进程首先要做的是设备的初始化工作,然后读取init.rc文件并启动系统中的重要外部程序Zygote。
Zygote进程是Android所有进程的孵化器进程,它启动后会首先初始化Dalvik虚拟机,然后启动system_server并进入Zygote模式,通过socket等候命令。
当执行一个Android应用程序时,system_server进程通过socket方式发送命令给Zygote,Zygote收到命令后通过fork自身创建一个Dalvik虚拟机的实例来执行应用程序的入口函数,这样一个程序就启动完成了。

Zygote提供了三种创建进程的方法:

fork(),创建一个Zygote进程;
forkAndSpecialize(),创建一个非Zygote进程;
forkSystemServer(),创建一个系统服务进程。
其中,Zygote 进程可以再 fork() 出其他进程,非Zygote进程 则不能 fork 其他进程,而 系统服务进程 在终止后它的子进程也必须终止。

 

3.3.2 Dalvik虚拟机执行程序流程
当进程fork成功后,执行的工作就交给了Dalvik虚拟机。

Dalvik虚拟机首先通过loadClassFromDex()函数完成类的装载工作,每个类被成功解析后都会拥有一个ClassObject类型的数据结构存储在运行时环境中,虚拟机使用gDvm.loadedClasses全局哈希表来存储与查询所有装载进来的类
随后,字节码验证器使用dvmVerifyCodeFlow() 函数对装入的代码进行校验
接着虚拟机调用FindClass() 函数查找并装载main方法类
随后调用dvmInterpret() 函数初始化解释器并执行字节码流。

 

4. Andorid 4.4 ——> 引入 ART 和 AOT
2013 年 10 月 31 日,Google 发布 Android 4.4 Kitkat,带来了全新的虚拟机运行环境 ART:Android RunTime 的预览版和全新的编译策略 AOT(Ahead-of-time)

Android Runtime (ART) 是 Android 上的应用和部分系统服务使用的托管式运行时。ART 及其前身 Dalvik 最初是专为 Android 项目打造的。作为运行时的 ART 可执行 Dalvik 可执行文件并遵循 Dex 字节码规范。

ART 和 Dalvik 是运行 Dex 字节码的兼容运行时,因此针对 Dalvik 开发的应用也能在 ART 环境中运作。不过,Dalvik 采用的一些技术并不适用于 ART。

需要注意的是,这时期 ART 是和 Dalvik 共存的,用户可以在两者之间进行选择(感觉怪怪的,用户可是小透明啊)
4.1 ART 主要功能
预先 (AOT) 编译
垃圾回收方面的优化
开发和调试方面的优化
4.2 AOT 编译模式的特点
优点 : 运行时省电 , 运行速度快
缺点 :
由于安装APK时触发dex2oat , 需要编译成native code , 导致安装时间过长
由于dex2oat生成的文件较大 , 会占用较多的空间

5. Android 5.0 ——> 全面使用 ART + AOT

5.1 A

参考 Android 8.0 中的 ART 功能改进

RT 的功能改进

AOT 模式解决了应用启动和运行速度和耗电问题的同时也带来了另外两个问题:

  • 应用安装和系统升级之后的应用优化比较耗时
  • 优化后的文件会占用额外的存储空间

 

2014 年 10 月 16 日,Google发布Android 5.0:Lollipop,ART 全面取代 Dalvik 成为 Android 虚拟机运行环境,至此,Dalvik 退出历史舞台,AOT 也成为唯一的编译模式。

AOT 和 JIT 的不同之处在于:

JIT 是在运行时进行编译,是动态编译,并且每次运行程序的时候都需要对 odex 重新进行编译
AOT 是静态编译,应用在安装的时候会启动 dex2oat 过程把 dex 预编译成 ELF 文件,每次运行程序的时候不用重新编译,是真正意义上的本地应用

 

6. Android 7.0 ——> JIT 回归
在 Android 5.x 和 6.x 的机器上,系统每次 OTA 升级完成重启的时候都会有个应用优化的过程,这个过程就是 dex2oat 过程,这个过程比较耗时并且会占用额外的存储空间。

2016年8月22日,Google发布Android 7.0(牛轧糖Nougat),JIT 编译器回归,形成 AOT/JIT 混合编译模式,这种混合编译模式的特点是:

应用在安装的时候 dex 不会被编译
应用在运行时 dex 文件先通过解析器(Interpreter)后会被直接执行(这一步骤跟 Android 2.2 - Android 4.4之前的行为一致),与此同时,热点函数(Hot Code)会被识别并被 JIT 编译后存储在 jit code cache 中并生成 profile 文件以记录热点函数的信息。
手机进入 IDLE(空闲) 或者 Charging(充电) 状态的时候,系统会扫描 App 目录下的 profile 文件并执行 AOT 过程进行编译。
可以看出,混合编译模式综合了 AOT 和 JIT 的各种优点,使得应用在安装速度加快的同时,运行速度、存储空间和耗电量等指标都得到了优化。
 

 

7.1 Android 7.0(Android N) 为什么安装速度更快?所需存储空间更小
安装速度变快

在Android N中,应用在安装时不再做编译,而是解释字节码。省去了冗长的编译时间,安装速度自然大大提升。

这就要归功于新加入的这个 JIT/AOT 混合编译技术了。新增的JIT编译器用于对ART进行代码分析,使之可以在应用运行时,持续优化Android应用的性能。使得安装时不做编译,也能达到与安装时完整编译一样的效果。这种编译模式我们依旧同称为AOT,只不过它的含义不再是预编译,而是全时编译技术(All Of the Time compilation)

此外,JIT的分析结果会被保存起来。当Android设备空闲或充电时,ART就会根据JIT的分析结果,将代码中的常用方法进行编译,而不常用的方法则待到需要时再编译,因而省下了部分存储空间。直观的体现就是我们安装完应用后,存储空间的占用变少了。

应用占用空间对比

根据实测,手机淘宝6.5.0安装完后在Android M中占用空间为171MB,而Android N中占用空间为156MB。
王者荣耀1.17.1.23安装完后在Android M中占用空间为439MB,在Android N中占用空间为428MB。
以下为个人推测:
王者荣耀在两个版本中的占用空间差没有手机淘宝的大的原因,很可能是因为手游中多为常用的交互代码,而手机淘宝中大多数交互都能在web中完成,因而不常用的代码可能更多。造成了这个现象。

总结
Android N安装应用快,是因其在安装时只解释字节码,省去了编译所用的时间。之所以能省去编译环节,是因其加入了全新的AOT全时编译技术,使得应用执行效率保持与Android M相同甚至更好。因而ART不再编译所有代码,所以省下了部分存储空间。

7.2 dexopt 与 dex2oat 的区别

 

前者针对 Dalvik 虚拟机,后者针对 Art 虚拟机。

dexopt 是对 dex 文件 进行 verification 和 optimization 的操作,其对 dex 文件的优化结果变成了 odex 文件,这个文件和 dex 文件很像,只是使用了一些优化操作码(譬如优化调用虚拟指令等)。

dex2oat 是对 dex 文件的 AOT 提前编译操作,其需要一个 dex 文件,然后对其进行编译,结果是一个本地可执行的 ELF 文件,可以直接被本地处理器执行。

除此之外在上图还可以看到 Dalvik 虚拟机中有使用 JIT 编译器,也就是说其也能将程序运行的热点 java 字节码编译成本地 code 执行,所以其与 Art 虚拟机还是有区别的。

Art 虚拟机的 dex2oat 是提前编译所有 dex 字节码,而 Dalvik 虚拟机只编译使用启发式检测中最频繁执行的热点字节码

7.3 Android各版本虚拟机 dexopt 产物的区别
5.0以下
使用Dalvik虚拟机 , 生成 odex 文件 . Dalvik采用的是JIT编译+解释器,也就是即时编译,每次应用运行时会实时将Dex翻译成机器码.

优点 : 安装速度超快 , 占用存储空间小
缺点 :
由于在Dex加载时会触发dexopt , 导致Multidex加载的时候会非常慢
由于热点代码的Monitor一直在运行 , 解释器解释的字节码会带来CPU和时间的消耗, 会带来电量的损耗
5.0 - 7.0
使用ART虚拟机 , 生成 oat 文件. 在ROM OTA或者恢复出场设置后 , 会要进行 dex2oat 根据当前ROM进行重新编译生成 .oat 文件.

优点 : 运行时省电 , 运行速度快
缺点 :
由于安装APK时触发dex2oat , 需要编译成native code , 导致安装时间过长
由于dex2oat生成的文件较大 , 会占用较多的空间
7.0 - 8.0
使用ART虚拟机 , 但是在7.0之上 , 增加了.vdex 与 .art 机制 , 在 ART 虚拟机再次启动/升级 , 加载 Dex/Oat 文件时 , 会减少 Dex 的校验时间 , 提升加载与运行效率

9.0
在 ART 虚拟机的基础上 , 增加了 Cdex ( Compat Dex )机制 ,

Compiler-fileter
在dex2oat的时候 , 会有一个目标编译类型 , 会有以下几类 , 根据时机不同dex2oat的编译方式也会不同

verify:只运行 DEX 代码验证。
quicken:运行 DEX 代码验证,并优化一些 DEX 指令,以获得更好的解译器性能。
speed:运行 DEX 代码验证,并对所有方法进行 AOT 编译。
speed-profile:运行 DEX 代码验证,并对配置文件中列出的方法进行 AOT 编译。

8. odex、vdex、cdex、art 文件

 

 

8.0 dex
dex(Dalvik VM Excutors):Dalvik 虚拟机执行程序,执行前需要优化

8.1 vdex
Android 8.0(Android O) 在 odex 的基础上又引入了 vdex 机制,目的是为了避免不必要的验证 dex 文件合法性以降低 dex2oa t时间

因为当系统ota后,用户自己安装的应用是不会发生任何变化的,但framework代码已经发生了变化,

所以就需要重新对这些应用也做dex2oat,所以如果有 vdex 的话,就可以省去重新校验 apk 里 dex 文件合法性的过程,节省一部分时间

主要目的:降低dex2oat执行耗时

当系统OTA后,对于安装在data分区下的app,因为它们的apk都没有任何变化,那么在首次开机时,对于这部分app如果有vdex文件存在的话,执行dexopt时就可以直接跳过verify流程,进入compile dex的流程,从而加速首次开机速度;
当app的jit profile信息变化时,background dexopt会在后台重新做dex2oat,因为有了vdex,这个时候也可以直接跳过
原理:

  • 应用首次安装时,抽取出其中的dex文件,校验成功后,存储到一个独立的文件中,后面由于jit profile改变,或OTA等原因,而重新进行dexopt时,可以跳过dex文件校验流程

8.2 odex
在Android N 之前,Dalvik虚拟机执行程序 dex文件前,系统会对dex文件做优化,生成可执行文件 odex,保存到 data/dalvik-cache 目录,最后把apk文件中的dex文件删除。

优点:

减少了启动时间(省去了系统第一次启动应用时从apk文件中读取dex文件,并对dex文件做优化的过程。)和对RAM的占用(apk文件中的dex如果不删除,同一个应用就会存在两个dex文件:apk中和 data/dalvik-cache 目录下)

防止第三方用户反编译系统的软件(odex文件是跟随系统环境变化的,改变环境会无法运行;而apk文件中又不包含dex文件,无法独立运行)。

在Android O 之后,odex 是从vdex 这个文件中 提取了部分模块生成的一个新的 可执行二进制码 文件 , odex 从vdex 中提取后,vdex 的大小就减少了。

第一次开机就会生成在 /system/app/<packagename>/oat/ 下

在系统运行过程中,虚拟机将其 从 /system/app 下 copy 到 /data/davilk-cache/ 下

odex + vdex = apk 的全部源码 (vdex 并不是独立于odex 的文件 odex + vdex 才代表一个apk )

8.3 .art
odex 进行优化生成的可执行二进制码文件,主要是apk 启动的常用函数相关地址的记录,方便寻址相关; 通常会在 data/dalvik-cache/ 保存常用的jar包的相关地址记录。

第一次开机不会生成在/system/app//oat/ 下,以后也不会;

odex 文件在运行时,虚拟机会计算函数调用频率,进行函数地址的修改, 最后在/data/davilk-cache/ 由虚拟机生成;

生成art 文件后,/system/app 下的 odex 和 vdex 会无效,即使你删除,apk也会正常运行

push 一个新的apk file 覆盖之前/system/app 下apk file ,会触发PKMS 扫描时下发force_dex flag ,强行生成新的vdex 文件 ,覆盖之前的vdex 文件,由于某种机制,这个新vdex 文件会copy到 /data/dalvik-cache/ 下,于是art文件也变化了。

8.4 oat
ART虚拟机使用的是 oat文件,oat 文件是一种Android私有 ELF文件格式,它不仅包含有从DEX文件翻译而来的本地机器指令,还包含有原来的DEX文件内容。

APK在安装的过程中,会通过 dex2oat 工具生成一个OAT文件。对于apk来说,oat文件实际上就是对odex文件的包装,即 oat=odex
参考链接
官方文档 – Android Runtime (ART) 和 Dalvik
抖音BoostMultiDex优化实践:Android低版本上APP首次启动时间减少80%(一)
安卓7.0为什么这么快?深入浅出解析秘密(读书笔记)
从ART的方向学习ODEX优化
进入Android Dalvik虚拟机
Dalvik优化和验证使用dexopt
Android各版本虚拟机的Dexopt区别
低版本 Android MultiDex 优化的三方库
Dalvik优化和验证使用dexopt
Android 8.0 VDEX机制简介
如何评价Android8.0引入的vdex?
————————————————
 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
深入理解Android虚拟机ART,需要对ART的原理、架构和功能进行全面的了解。 ART(Android Runtime)是Android操作系统中的一种虚拟机,它在Android 5.0及以后的版本中取代了Dalvik虚拟机。ART通过对应用程序的预编译,将字节码转换为机器码,提供了更高的性能和更低的内存占用。 ART的内部架构由多个模块组成,包括编译器、运行时库和垃圾回收器。编译器模块负责将应用程序的字节码转换为本地机器码,采用了提前编译(Ahead-of-Time Compilation)的方式,将代码的热点部分提前编译为本地机器码,从而加速应用程序的执行。运行时库模块提供了与设备硬件和操作系统交互的接口,同时实现了一些Java虚拟机的功能,如线程管理和异常处理。垃圾回收器模块负责管理内存资源,通过回收不再使用的对象,提供了更好的内存管理能力。 ART提供了一些新的特性,如增强的垃圾回收、即时编译和应用程序优化等。其中,增强的垃圾回收机制使用了新的分代垃圾回收算法,能更好地管理内存资源,减少应用程序的内存占用。即时编译(Just-In-Time Compilation)可以将应用程序的热点代码实时编译为机器码,在应用程序的执行过程中提升性能。应用程序优化功能可以分析应用程序的运行状况,根据实际情况进行优化,提供更好的用户体验。 总之,深入理解Android虚拟机ART需要详细了解其原理、架构和功能,同时还需要研究相关的性能优化方法和工具。只有全面了解ART,才能更好地开发和优化Android应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值