虚拟机编译策略

虚拟机的作用

虚拟机通过将代码翻译为二进制,起了一个解释器的作用。

其完整的步骤大致如下:

一个.java文件,通过javac编译为后缀为.class的字节码文件,然后虚拟机通过对应的机器接口,一边解释这个字节码文件为二进制,一边运行。

其解释运行的步骤如下:

解释执行(AOT):将编译好的字节码一行一行地翻译为机器码执行。

通过解释执行消除了平台差异性。

但事实上还有一种执行方法:

编译执行(JIT):以方法为单位,将字节码一次性翻译为机器码后执行。

解释执行的好处类似于饿汉,编译执行就是懒汉。前者优势在于不用等待,后者优势在于效率更高

Dalvik虚拟机

主要罗列一下与普通jvm不同的地方

jvm虚拟机运行的是java字节码,dalvik运行的是dalvik字节码

dalvik字节码其实就是java字节码转换而来并且打包到一个dex可执行文件中

dalvik可执行文件体积更小

为了减小运行文件的体积,安卓使用Dalvik虚拟机,SDK中有个dx工具负责将JAVA字节码转换为Dalvik字节码。dx工具对JAVA类文件又一次排列,将全部JAVA类文件里的常量池分解,消除当中的冗余信息。又一次组合形成一个常量池。全部的类文件共享同一个常量池。使得相同的字符串、常量在DEX文件里仅仅出现一次,从而减小了文件的体积。

jvm基于栈,dalvik基于寄存器

因此一定程度上代码指令集降低,增加了速度

ART虚拟机

Dalvik执行的是dex字节码,依靠JIT编译器去解释执行,运行时动态地将执行频率很高的dex字节码翻译成本地机器码,然后在执行,但是将dex字节码翻译成本地机器码是发生在应用程序的运行过程中,并且应用程序每一次重新运行的时候,都要重新做这个翻译工作,因此,及时采用了JIT,Dalvik虚拟机的总体性能还是不能与直接执行本地机器码的ART虚拟机相比。

安卓运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者重新将自己的应用直接编译成目标机器码,也就是说,应用程序仍然是一个包含dex字节码的apk文件。所以在安装应用的时候,dex中的字节码将被编译成本地机器码,之后每次打开应用,执行的都是本地机器码。移除了运行时的解释执行,效率更高,启动更快。(安卓在4.4中发布了ART运行时)

1
2
3
4
Android 运行时 (ART) 包含一个具备代码分析功能的即时 (JIT) 编译器,该编译器可以在 Android 应用运行时持续提高其性能。JIT 编译器补充了 ART 当前的预先 (AOT) 编译器的功能,有助于提高运行时性能,节省存储空间,以及加快应用及系统更新速度。相较于 AOT 编译器,JIT 编译器的优势也更为明显,因为它不会在应用自动更新期间或重新编译应用(在无线下载 (OTA) 更新期间)时拖慢系统速度。

尽管 JIT 和 AOT 使用相同的编译器,它们所进行的一系列优化也较为相似,但它们生成的代码可能会有所不同。JIT 会利用运行时类型信息,可以更高效地进行内联,并可让堆栈替换 (OSR) 编译成为可能,而这一切都会使其生成的代码略有不同。

JIT架构

jit编译步骤

  1. 用户运行应用,而这随后就会触发 ART 加载 .dex 文件。
  • 如果有 .oat 文件(即 .dex 文件的 AOT 二进制文件),则 ART 会直接使用该文件。虽然 .oat 文件会定期生成,但文件中不一定会包含经过编译的代码(即 AOT 二进制文件)。

  • 如果 .oat 文件不包含编译的代码,ART 会通过 JIT 和解释器执行 .dex 文件。

  1. 针对任何未根据 speed 编译过滤器编译的应用启用 JIT(也就是说,要尽可能多地编译应用中的代码)。

  2. 将 JIT 配置文件数据转存到只限应用访问的系统目录内的文件中。

  3. AOT 编译 (dex2oat) 守护进程通过解析该文件来推进其编译。

jit工作流程

JIT工作流程

  • 分析信息会存储在代码缓存中,并会在内存紧张时作为垃圾被回收。

    • 无法保证在应用处于后台运行状态时所捕获的快照能够包含完整的数据(即 JIT 编译的所有内容)。
    • 该过程不会尝试确保记录所有内容(因为这将影响运行时性能)。
  • 方法可能有三种不同的状态:

    • 已经过解释(dex 代码)
    • 已经过 JIT 编译
    • 已经过 AOT 编译
      如果同时存在 JIT 和 AOT 代码(例如,由于反复进行逆优化),经过 JIT 编译的代码将是首选代码。

在不影响前台应用性能的情况下运行 JIT 所需的内存取决于相关应用。大型应用比小型应用需要更多内存。一般来说,大型应用所需的内存稳定维持在 4 MB 左右。

JIT相关命令

开启JIT日志记录

1
2
3
4
adb root
adb shell stop
adb shell setprop dalvik.vm.extra-opts -verbose:jit
adb shell start

停用JIT命令

1
2
3
4
adb root
adb shell stop
adb shell setprop dalvik.vm.usejit false
adb shell start

强制编译命令

1
adb shell cmd package compile

基于配置文件强制编译

1
adb shell cmd package compile -m speed-profile -f my-package

全面编译

1
adb shell cmd package compile -m speed -f my-package

要清除配置文件数据并移除经过编译的代码

只清除一个软件包

1
adb shell cmd package compile --reset my-package

清除所有软件包

1
adb shell cmd package compile --reset -a

art虚拟机优缺点

优点:
1.系统性能显著提升
2.应用启动更快、运行更快、体验更流畅、触感反馈更及时
3.续航能力提升
4.支持更低的硬件

缺点:
1.更大的存储空间占用,可能增加10%-20%
2.更长的应用安装时间
3.启动会占用一点点的运行内存,不大,大约4~10,官网有介绍

华为方舟编译器初探

因为暂时没有源码,先看一下ppt

余承东的ppt上面这么写的

华为方舟1

说传统app是一边解释一边运行的,从上面的学习事实上可以看出来,不论是dalvik,还是art,事实上都有jit编译的影子。但art在理想情况下其实很大部分是aot编译。

华为方舟给我的感觉,像是将AOT从编译时提前到了代码打包的时候。应该是将AOT编译结果.oat文件存储到压缩包内,安装的时候应该会放在appdata里面。

但是如果是这样的话,可能会有点问题,之所以aot放在了安装的时候进行,主要是因为dalvik不支持的原因。华为这样做,放弃了dalvik?6.9%的用户啊。

另外aot提前的话,个人感觉profile并没有失去作用,毕竟可以通过jit再次提升速度。

另外hot fix怎么办呢?bugly这种通过.dex文件进行对比的怎么办呢?难道说tinker会根据华为的方舟,专门推出一个基于机器码对比的tinker?不现实啊。

目前仅限于猜想,等源码出来之后在研究一下

基于华为人员介绍的方舟编译器总结

上周华为的方舟编译器来做宣传,希望头部厂商使用,亏了公司的平台能够接触这个。

首先他们介绍了方舟的来源,起先是做编译器的一群人弄的,由GCC衍生出了hcc,同时推出了cm语言,直介绍了这么些。

之后就开始介绍方舟编译器。

首先为什么能做到类似于AOT的效果呢?是在APK打包为apk之后,解压出dex文件,然后将dex通过方舟那边的编译器,转换为so,这样就可以直接执行了,而不需要java那样的解释。

所以在对比aot来讲,仍然是快了,Android的aot还不是严格的aot,是aot和jit的合体,因此速度更慢了。

但是华为因为是直接变为so库进行加载的,所以暴露了很多问题。

基于架构方面的,华为的so只支持ARM64架构。

基于运行时方面,华为的so需要自己的运行时,而非clib。

由于是so,所以不支持热更新,插件化。

由于是so,所以不能加壳。

由于是so,所以不在使用gc的方式回收,而是所谓的RC。由于使用了Rc,所以回收是通过作用域,而非gc扫描这种,所以不会出现多峰值问题,但是这也就是将高峰值切到平时来处理了。

由于是dex转为so,所以占用空间大3倍左右。

由于安装时就是机器码。所以失去了Android的profile机制,这样就无法进行持续性优化。

大体就是如上的。

好处也是有的,不单单是峰值内存降低的问题,其虽然是个so,但是崩溃仍然显示的是java的crash栈,另外几乎将所有的java层到jni部分的代码全部改为了so

感想

没想到是通过生成so的方式,有点惊艳,华为的方舟不单单是Android的编译器,其自己还做了很多编译器,像js的,php的,c的等等。但是这次谷歌的封锁的确是影响很大,不单单是他们自己做了自己的编译器,同时还禁止了fushia…

华为加油吧,但是我们产品由于目前toB 不会上架应用市场,且车载的基本上都是32位的,所以我们是暂时无法使用这个。后续自己做apk的话,可以上这个。