聊聊ART虚拟机_如何保证高效执行
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情
ART 系列
聊聊ART虚拟机_对象的分配问题 - 掘金 (juejin.cn)
前置知识
- 有Android开发基础
- 了解 Java 语法和 JVM
- 已阅读 聊聊ART虚拟机_对象的分配问题、聊聊ART虚拟机_对象的使用和销毁问题 )两篇文章
前言
我们在前面两篇文章中聊到了 ART 虚拟机中对于对象的分配、使用和销毁的问题。主要了解其如何分配到对应的内存、通过几种手段来使用,且如何 ART 是如何最好的解决内存碎片的问题。
那么本文中,我们就继续聊一聊 ART 是如何使用几种方式来保持高效执行的。
执行方式
前文中我们有提到,在 ART 中的执行方式有三种:包括解释执行、JIT 执行和 AOT 执行。
解释执行
其实就是去解释翻译 APK 包内的 .dex 代码,这个.dex 代码是所有 .class 文件的一个总集合,是由 Java代码翻译而来的。最后再由 ART 去解释执行。
下图中,我们打开 APK 文件可以看到对应的 .dex 文件。
JIT 执行
对于 JIT 解释执行,它会在解释执行的时候,他会基于线程的执行位置以及优先级生成 profile 文件以及对此打分,如果分数达标,就会提交给 JIT 编译器编译以及进行 OSR 替换。(此处的打分只是一个形象的比喻)而对应的函数下一次执行的时候,就会换成 JIT 执行了,而不再是解释执行。
AOT 执行
AOT 是在程序运行之前,对 APK 中的函数进行编译。不像 JIT 是在程序执行阶段编译的。其有如下几个特点:
- 和程序是否允许无关
- 编译以 dex 为单位,而不是以函数为单位
- 结果持久化
AOT 与 JIT 的不一样的地方就是:
- 不需要维持一块内存来保持 JIT 的结果
- 不需要预热
- 无法动态调整
上述的三类执行方式中,JIT 和 AOT 的方式的效率必然是要大于直接解释执行的。所以,在 ART 中,他们使用了以上的多种方式来进行结合执行,以提高 ART 的整体运行效率。
延迟绑定
对于上面的 AOT 与 JIT ,为何 AOT 就会比 JIT 更优呢?这是由于绑定时间的不同所导致的。
绑定得越迟,动态性越好,性能越差
绑定得越早,动态性越差,性能越好
例如 Java 中对象的分配问题,是需要运行前进行继承链的寻找的,这就是延迟绑定,但是其动态性会好很多。例如在 Android 的 View 体系中,他也是延迟绑定的。其不能在编译阶段就确定对应使用的父类 View 是哪个,需要在安装的时候才能确定好,因为不同的 Android 版本的 View 类是不一致的,所以不能一开始就确定好父类,需要在安装好的时候才去确定。
栈管理
在 Java 中,基本类型变量和对象的引用都是放置在栈内存中的。而上述的三类执行方式,其对于栈的管理方式也是不同的。大体分为两类,解释执行是一类,而 JIT 和 AOT 的编译后执行又是一类。
解释执行部分,其将栈全权托管给虚拟机完成的;而对于编译后执行的一类,其遵从对应指令集的约定来对栈进行管理,其与 C++ 的约定类似,指定某些寄存器来进行执行和管理。
而这两种不同的栈管理方式之间,是可以采用一些机制来不断切换的。有兴趣的同学可以自行了解。
需要注意的是:栈管理中是和异常抛出有联系的,异常执行中的 catch() 是一种回栈行为。
多线程与同步问题
在 java 中,可能会出现线程不同步的问题,例如两个线程共享一个变量的时候会出现获得的两个变量的值不一致的问题,因为在 Java 中,是默认对变量复制之后,再去操作复制的的变量的,所以会导致这个不同步的问题。
所以,为了解决这个问题,会需要使用同步机制。我们可以使用 synchronize
语句来加锁解锁,而在前文中提到的 monitor
的信息就是保存锁的信息,其先生成胖锁,后转化为瘦锁。
瘦锁(spinLock):由锁主动去响应,但是消耗很大
胖锁(mutexlock):由执行方去通知锁,但是消耗小
胖锁和瘦锁的切换机制,但要自定义只使用瘦锁的时候,可以使用自定义的 volatile
来实现
volatile
:解决三个问题。语序重拍,读时未同步,写时被修改。实现原理是基于读写栏栅处理cpu读写顺序,同时处理编译时候严格按照编码顺序处理。
参考
JIT(动态编译)和AOT(静态编译)编译技术比较_Adenialzz的博客-CSDN博客_aot技术
android synchronized原理分析(二) Monitor_小二哥m的博客-CSDN博客