1.前言
首先希望觉得本片文章有用的童鞋们记得点赞!关注!收藏!,不要吝啬你们的称赞啦!
Android的虚拟机经过演变,从Android5.0前的Dalvik虚拟机在Android5.0后全面变成了ART虚拟机,这两个虚拟机都是基于寄存器的,而我们的JVM是基于栈的。
那为什么安卓的虚拟机是基于寄存器,跟JVM基于栈有所不同呢?最大的原因肯定是因为移动端的硬件环境进行了针对性相关优化,比如安卓里面字节码文件是.dex而不class,那正是谷歌对移动端进行了相关针对性的优化,那这个时候就要将JVM基于栈的模式与基于寄存器的模式进行一个对比,我们才能了解为什么安卓虚拟机会基于寄存器。
2.栈与寄存器的区别
在介绍JVM之前,我们先了解栈与寄存器的区别是什么?
先上结论!!!
所以为什么Android平台基于寄存器的设计?
- 指令条数少,所以执行的效率更快!
- 数据移动次数少、临时结果存放次数少,所以更省内存和执行效率更快!
- 映射真实机器的寄存器,所以执行效率更快!
2.1.栈
栈的特点是:FILO(先进后出,Fist In Last Out)
栈的出入口只有一个,那就意味着先进的元素会在栈底,而后进的元素会在栈底,先进的元素不能直接出栈,需要栈顶的元素出栈后,才能出栈,所以栈是一个单向队列的数据结构,我们先上图来描述一下栈。
我们可以看出,A元素是首先进入栈的,这个时候会被处于栈顶,随着B元素的进入,A元素被压入栈底,而B元素处于栈顶,最后C元素压入栈中,成为了栈顶,这个时候A、B元素出栈都意味着C元素必须要出栈。
Ok,栈的概念还是比较容易明白的,接下来我们看看寄存器的概念。
2.2.寄存器
寄存器相当于CPU里面的内存,它本身是有固定的内存地址的,这意味着数据在寄存器中的操作/IO是非常快速的。
寄存器的指令一般较长,在Android里面每个指令占2个字节,但是指令条数会大幅减少,而JVM虚拟站中指令占一个字符,但是指令条数会大幅增加,能快速执行。
那么简单介绍完栈与寄存器的概念之后,我们就进入下一章,JVM是怎么通过虚拟栈来执行指定的?
3.JVM是如何通过虚拟栈执行字节码文件的?
3.1 了解JVM运行时的数据区组成
首先我们要了解JVM运行时的数据区,看一幅图:
我们可以由上图看出,ClassLoader将.class文件加载至运行时数据区,运行时数据区其实就是字节码被加载到内存后在内存的一个状态,我们可以看出有以下几部分。
- 堆
存放着实例的区域,而GC主要也是针对该区域。
- 方法区
存放着从字节码中加载出来的类信息。
- 线程独立区域
- 程序计数器
保存程序字节码执行的顺序 - 虚拟机栈
描述java方法执行的内存模型 - 本地方法栈
本地方法栈则为Native 方法服务
- 程序计数器
简单了解了运行时数据区的构造后,接下来我们将深入到JVM中的线程是如何基于虚拟机栈来执行字节码的。
3.2 了解栈帧
我们知道虚拟机栈是负责描述java方法执行的内存模型,并且记录线程内方法的执行状态的,而栈中的元素,也会被称为栈帧,比如我们现在执行一个main方法,main方法中再执行一个foo方法,那么虚拟机栈中就会存在两个栈帧,如图:
那如果此时我们的虚拟机栈中的foo方法含有相关逻辑,虚拟机栈又是如何执行这些逻辑的呢?
首先,我们的虚拟机栈是如何执行以上字节码的呢?首先我们了解一下栈帧的组成
我们可以看出一个栈帧里面含有
- 局部变量表
局部变量表存放着方法中的局部变量+this实例
- 操作数栈
用于字节码执行时,暂存其中间状态的一个内存区域,每个操作数栈的容量是有限的,在编译期会推断其容量大小并且记录在字节码文件中,而JVM说的基于栈,其实就是基于该操作数栈。
- 方法出口
记录该方法的出口,需要返回什么,返回给谁。
- 程序计数器
记录着程序的执行顺序。
3.3 了解栈是如何执行逻辑的
接下来我们定义一个foo方法,并且编译成class文件,然后我们解析一下虚拟机栈是怎么执行字节码程序的。
public void foo() {
int a = 1;
int b = 2;
int c = (a + b)*9;
}
然后我们将其解析成字节码后是这样的
顺序 | 字节码 | 助记符 |
---|---|---|
0 | 0x04 | iconst_1 |
1 | 0x3b | istore_0 |
2 | 0x05 | iconst_2 |
3 | 0x3c | istore_1 |
4 | 0x1a | iload_0 |
5 | 0x1b | uload_1 |
6 | 0x60 | iadd |
7 | 0x10 0x09 | bipush9 |
9 | 0x67 | imul |
a | 0x3d | istore_2 |
b | 0xb1 | return |
那么栈帧中是如何执行上面的字节码的?
我们以图解的形式解析全过程,首先是一个栈帧的初始化状态,局部变量表与操作数栈的状态如下:
我们可以看出局部变量表中初始化了a,b,c三个变量,而操作数栈中目前容量为2,那么我们开始执行每一步字节码。
至于最后一行字节码,其实从助记符中可以明显的判断出,方法返回结束了~就不特意贴图了
(从第八张图开始,因为换了分辨率去制图,所以字体变小了,将就看。。)
看完整个执行过程最直观的我们能看出以下几点:
- 字节码大部分时候占1位
- 局部变量表中暂存数据,字节码中不负责保存
- 操作基本都是基于操作数栈进行操作
既然我们要作对比,基于栈和基于寄存器的区别是什么?那么接下来就来讲讲寄存器执行逻辑的过程,然后再作一个对比~
4.ART是如何基于虚拟寄存器执行字节码文件的?
4.1 结构变化
对比基于栈,基于寄存器的虚拟机栈帧构成如下:
我们可以看出:
- 没有了操作数栈、被虚拟寄存器代替了、
- 也没有了变量表
- 保留了程序计数器
乍一看好像做了不少减法,直观的感受到执行时占用内存的减少。
ok,了解栈帧中的变化,那么接着看我们基于寄存器的字节码有哪些变化。
4.2 字节码变化
我们可以直观的看出指令数减少了,但是每个字节码从占用一个字节升级到两个字节,接着我们看看寄存器是怎么执行逻辑的~
4.3 解寄存器是如何执行逻辑的?
最后一步也不贴了,就是return方法~
那么我们可以清晰的感知到,因为栈帧结构变化,没有了局部变量表,所以字节码中不需要再执行将数据保存到变量表相关的操作,大大的减少了流程,但是我们看最后一张图,有没有发现少了什么?
方法中 var a的值不见了!
不要慌,这其实是编译优化,我们以最少的寄存器为前提,不改变语意,优化掉无用的代码。
ok,了解了他们的栈帧构造、字节码区别、执行逻辑后,我们进入对比环节!
5.对比环节
直接总结好~上图表!
对比项 | 基于栈 | 基于寄存器 |
---|---|---|
字节码单元长度 | 8位(1字节) | 16位(2字节) |
单条指令长度 | 短 | 几乎翻倍 |
同样逻辑指令条数 | 多 | 少 |
同样逻辑数据移动次数 | 多 | 尽可能少 |
同样逻辑临时结果存储次数 | 多 | 尽可能少 |
所以为什么Android平台基于寄存器的设计?
- 指令条数少,所以执行的效率更快!
- 数据移动次数少、临时结果存放次数少,所以更省内存和执行效率更快!
- 映射真实机器的寄存器,所以执行效率更快!
以上就是《为什么Android平台虚拟机基于寄存器的设计?》的全部内容,觉得本片文章有用的童鞋们记得点赞!关注!收藏!,不要吝啬你们的称赞啦啦啦啦啦