企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] ## Android虚拟机 ### android2.2~android4.4(JIT与Dalvik) JIT是运行时编译,这样可以对执行次数频繁的dex代码进行编译和优化,减少以后使用时的翻译时间,虽然可以加快Dalvik运行速度,但是还是有弊病,那就是将dex翻译为本地机器码也要占用时间。 ***** JIT是"Just In Time"的缩写,就是"即时编译技术",与Dalvik虚拟机相关 我们使用Java开发android,在编译打包APK文件时,会经过以下流程 * Java编译器将应用中所有Java文件编译为class文件 * dx工具将应用编译输出的类文件转换为Dalvik字节码,即dex文件 * 之后经过签名、对齐等操作变为APK文件。 Dalvik虚拟机可以看做是一个Java VM,他负责解释dex文件为机器码,如果我们不做处理的话,每次执行代码,都需要Dalvik将dex代码翻译为微处理器指令,然后交给系统处理,这样效率不高。 为了解决这个问题,Google在2.2版本添加了JIT编译器,当App运行时,每当遇到一个新类,JIT编译器就会对这个类进行编译,经过编译后的代码,会被优化成相当精简的原生型指令码(即native code),这样在下次执行到相同逻辑的时候,速度就会更快。 ### android4.4~android5.0(Dalvik和art并存) Google在4.4之后推出了ART,用来替换Dalvik。4.4之后两种运行时环境共存(JIT 和 ANR),可以相互切换,但是在5.0+,Dalvik虚拟机则被彻底的丢弃,全部采用ART. ### android5.0+(art) ART的策略与Dalvik不同,在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。之后打开App的时候,不需要额外的翻译工作,直接使用本地机器码运行,因此运行速度提高。 当然ART与Dalvik相比,还是有缺点的。 * ART需要应用程序在安装时,就把程序代码转换成机器语言,所以这会消耗掉更多的存储空间,但消耗掉空间的增幅通常不会超过应用代码包大小的20% * 由于有了一个转码的过程,所以应用安装时间难免会延长。 ### android7.0+(ART 即时 (JIT) 编译器) 一个 app 在安装时会生成相应的 oat 文件。在运行时,会加载其 oat 及 dex 文件。执行一个方法,可能有三种模式:**解释执行**、**执行 oat 机器码**、**JIT**。 Android 7.0 之后,为了加载 app 的安装速度,安装时生成 oat 文件只会做 smali 指令优化等少量优化工作,并不会立刻生成 oat 机器码。这也意味着 app 在安装之初运行是解释执行为主的。 在解释执行过程中,ART 会记录每个方法的调用次数,记录在方法对应的 ArtMethod 中。这个次数即该方法的热度。方法的热度达到设定值之后,这个方法会被标记为**热代码**,并触发 JIT,在运行时编译生成机器码,以便下次调用时直接执行机器码。这些热代码还在被记录到 app 的 profile 中,以便之后重新 oat 时编译这些热代码。 方法的三种执行模式可以在运行时动态切换,实际上同一进程中三种模式往往是共存的,而且同一方法的运行模式也可能随时切换。 ## 内联优化 ### 什么是内联及内联的生成时机 内联所做的事情就是 * 缩短调用链 * 缩短代码 * 提高运行速度 ### 常见的内联类型 比较常见的内联类型有常量内联,字符串内联以及方法内联等常量内联 #### 常量内联 修饰的静态常量值直接替换到代码块里面,比较常见的是作用于编译期,可以体现在.class文件中。 #### 方法内联 方法内联是将被调用方的代码拷贝到调用方的调用处,从而减少代码函数定义及函数跳转等,提高运行效率。 根据我们下文的对生成时机的区分,还可细分为编译期内联优化,dex2oat内联优化和JIT内联优化 ##### 1\. 编译期内联优化(混淆优化工具R8) 当Android Gradle插件升级至3.4.0,会引入一个新的混淆优化工具R8,这个工具可以对代码进行压缩优化,其中就包括了选择性内联。 (脱糖)Desugaring的工作原理是 R8/D8在字节码编译成Dex文件时,通过三方库的引入支持了原本不支持的高版本JDK语法,并一同打进dex,使之可以正常工作 使用R8优化包体积 R8在D8的基础上又增加了Proguard的能力,通过开启Shrinking,可以对源码进行优化,以达到减小包体积的目的,具体包括但不限于: * 代码删除:通过语法树静态分析技术,发现并剔除未使用的代码,例如没有被实例化的Class等 * 代码优化:对运行时代码进行优化,包括死代码删除、未使用的参数删除、选择性内联、类合并等。 * 代码混淆:优化标识符名字,减少代码数量,例如MyAwesomeClass可能被优化成单一字母a ##### 2\. dex2oat内联优化(dex不可见优化) ART(Android Runtime)是Android在4.4版本中引入的新虚拟机环境,在5.0版本正式取代了Dalvik VM。ART环境下,App安装时其包含的Dex文件将被dex2oat预编译成目标平台的机器码oat,从而提高了App的运行效率。在这个预编译过程中,dex2oat对目标代码的优化过程与Dalvik VM下的dexopt有较大区别,尤其是在5.0版本以后ART环境下新增的方法内联优化,改变了原本的方法分布和调用流程。优化dex2oat时方法内联优化的条件 dex2oat时,方法被内联优化的条件是实现在ART的源码中的,每个android版本都有一些明显的差异区别。内联优化特性是从kOptimizing Compiler引入的,最早出现在android 6.x,但是android 6.x默认使用的是kQuick Compiler。而到了android 7.x,kOptimizing Compiler成了默认的Compiler类型。对于Optimizing Compiler,当以下条件均满足时被调用的方法将被inline(仅针对Android N版本): * App不是Debug版本的; * 被调用的方法所在的类与调用者所在的类位于同一个Dex;(注意,符合Class N命名规则的多个Dex要看成同一个Dex) * 被调用的方法的字节码条数不超过dex2oat通过--inline-max-code-units指定的值,6.x默认为100,7.x默认为32; * 被调用的方法不含try块; * 被调用的方法不含非法字节码; * 被调用方法还不能包含对接口方法的调用。 此外,Optimizing Compiler的方法内联可以跨多级方法调用进行,若有这样的调用链:method1->method2->method3->method4,则在四个方法都满足内联条件的情况下,最终内联的结果将是method1包含method2,method3,method4的代码,method2包含method3,method4的代码,以此类推。但这种跨调用链内联会受到调用dex2oat时通过--inline-depth-limit参数指定的值的限制,默认为5,即超过5层的调用就不会再被内联到当前方法了。 ##### 3\. JIT内联优化(dex不可见优化) ART也包含一个具备代码分析功能的即时 (JIT) 编译器,该编译器可以在 Android 应用运行时持续提高其性能。JIT 会利用运行时类型信息,可以更高效地进行内联,并可让堆栈替换 (OSR) 编译成为可能。有些小伙伴对方法内联有个印象是频繁被调用的方法会发生内联,这一块工作就是JIT在运行时完成的。