为什么Android平台的虚拟机是基于寄存器设计的?

1.前言

首先希望觉得本片文章有用的童鞋们记得点赞!关注!收藏!,不要吝啬你们的称赞啦!

Android的虚拟机经过演变,从Android5.0前的Dalvik虚拟机在Android5.0后全面变成了ART虚拟机,这两个虚拟机都是基于寄存器的,而我们的JVM是基于栈的。

那为什么安卓的虚拟机是基于寄存器,跟JVM基于栈有所不同呢?最大的原因肯定是因为移动端的硬件环境进行了针对性相关优化,比如安卓里面字节码文件是.dex而不class,那正是谷歌对移动端进行了相关针对性的优化,那这个时候就要将JVM基于栈的模式与基于寄存器的模式进行一个对比,我们才能了解为什么安卓虚拟机会基于寄存器。

2.栈与寄存器的区别

在介绍JVM之前,我们先了解栈与寄存器的区别是什么?

先上结论!!!

所以为什么Android平台基于寄存器的设计?

  1. 指令条数少,所以执行的效率更快!
  2. 数据移动次数少、临时结果存放次数少,所以更省内存和执行效率更快!
  3. 映射真实机器的寄存器,所以执行效率更快!

2.1.栈

栈的特点是:FILO(先进后出,Fist In Last Out)

栈的出入口只有一个,那就意味着先进的元素会在栈底,而后进的元素会在栈底,先进的元素不能直接出栈,需要栈顶的元素出栈后,才能出栈,所以栈是一个单向队列的数据结构,我们先上图来描述一下栈。

栈.png

我们可以看出,A元素是首先进入栈的,这个时候会被处于栈顶,随着B元素的进入,A元素被压入栈底,而B元素处于栈顶,最后C元素压入栈中,成为了栈顶,这个时候A、B元素出栈都意味着C元素必须要出栈。

Ok,栈的概念还是比较容易明白的,接下来我们看看寄存器的概念。

2.2.寄存器

寄存器相当于CPU里面的内存,它本身是有固定的内存地址的,这意味着数据在寄存器中的操作/IO是非常快速的。

寄存器的指令一般较长,在Android里面每个指令占2个字节,但是指令条数会大幅减少,而JVM虚拟站中指令占一个字符,但是指令条数会大幅增加,能快速执行。

那么简单介绍完栈与寄存器的概念之后,我们就进入下一章,JVM是怎么通过虚拟栈来执行指定的?

3.JVM是如何通过虚拟栈执行字节码文件的?

3.1 了解JVM运行时的数据区组成

首先我们要了解JVM运行时的数据区,看一幅图:

运行时数据区.png

我们可以由上图看出,ClassLoader将.class文件加载至运行时数据区,运行时数据区其实就是字节码被加载到内存后在内存的一个状态,我们可以看出有以下几部分。

存放着实例的区域,而GC主要也是针对该区域。

  1. 方法区

存放着从字节码中加载出来的类信息。

  1. 线程独立区域
    1. 程序计数器
      保存程序字节码执行的顺序
    2. 虚拟机栈
      描述java方法执行的内存模型
    3. 本地方法栈
      本地方法栈则为Native 方法服务

简单了解了运行时数据区的构造后,接下来我们将深入到JVM中的线程是如何基于虚拟机栈来执行字节码的。

3.2 了解栈帧

我们知道虚拟机栈是负责描述java方法执行的内存模型,并且记录线程内方法的执行状态的,而栈中的元素,也会被称为栈帧,比如我们现在执行一个main方法,main方法中再执行一个foo方法,那么虚拟机栈中就会存在两个栈帧,如图:

栈帧.png

那如果此时我们的虚拟机栈中的foo方法含有相关逻辑,虚拟机栈又是如何执行这些逻辑的呢?

首先,我们的虚拟机栈是如何执行以上字节码的呢?首先我们了解一下栈帧的组成

栈帧组成.png

我们可以看出一个栈帧里面含有

  1. 局部变量表

局部变量表存放着方法中的局部变量+this实例

  1. 操作数栈

用于字节码执行时,暂存其中间状态的一个内存区域,每个操作数栈的容量是有限的,在编译期会推断其容量大小并且记录在字节码文件中,而JVM说的基于栈,其实就是基于该操作数栈。

  1. 方法出口

记录该方法的出口,需要返回什么,返回给谁。

  1. 程序计数器

记录着程序的执行顺序。

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

那么栈帧中是如何执行上面的字节码的?

我们以图解的形式解析全过程,首先是一个栈帧的初始化状态,局部变量表与操作数栈的状态如下:

栈帧运行时1.png

我们可以看出局部变量表中初始化了a,b,c三个变量,而操作数栈中目前容量为2,那么我们开始执行每一步字节码。

栈帧运行时2.png栈帧运行时3.png栈帧运行时4.png栈帧运行时5.png栈帧运行时6.png栈帧运行时7.png栈帧运行时8.png栈帧运行时9.png栈帧运行时10.png

至于最后一行字节码,其实从助记符中可以明显的判断出,方法返回结束了~就不特意贴图了

(从第八张图开始,因为换了分辨率去制图,所以字体变小了,将就看。。)

看完整个执行过程最直观的我们能看出以下几点:

  1. 字节码大部分时候占1位
  2. 局部变量表中暂存数据,字节码中不负责保存
  3. 操作基本都是基于操作数栈进行操作

既然我们要作对比,基于栈和基于寄存器的区别是什么?那么接下来就来讲讲寄存器执行逻辑的过程,然后再作一个对比~

4.ART是如何基于虚拟寄存器执行字节码文件的?

4.1 结构变化

对比基于栈,基于寄存器的虚拟机栈帧构成如下:

寄存器组成.png

我们可以看出:

  1. 没有了操作数栈、被虚拟寄存器代替了、
  2. 也没有了变量表
  3. 保留了程序计数器

乍一看好像做了不少减法,直观的感受到执行时占用内存的减少。

ok,了解栈帧中的变化,那么接着看我们基于寄存器的字节码有哪些变化。

4.2 字节码变化

寄存器字节码.png

我们可以直观的看出指令数减少了,但是每个字节码从占用一个字节升级到两个字节,接着我们看看寄存器是怎么执行逻辑的~

4.3 解寄存器是如何执行逻辑的?

寄存器运行时1.png

寄存器运行时2.png

寄存器运行时3.png

寄存器运行时4.png

寄存器运行时5.png

最后一步也不贴了,就是return方法~

那么我们可以清晰的感知到,因为栈帧结构变化,没有了局部变量表,所以字节码中不需要再执行将数据保存到变量表相关的操作,大大的减少了流程,但是我们看最后一张图,有没有发现少了什么?

方法中 var a的值不见了!

vara不见了.png

不要慌,这其实是编译优化,我们以最少的寄存器为前提,不改变语意,优化掉无用的代码。

ok,了解了他们的栈帧构造、字节码区别、执行逻辑后,我们进入对比环节!

5.对比环节

直接总结好~上图表!

对比项 基于栈 基于寄存器
字节码单元长度 8位(1字节) 16位(2字节)
单条指令长度 几乎翻倍
同样逻辑指令条数
同样逻辑数据移动次数 尽可能少
同样逻辑临时结果存储次数 尽可能少

所以为什么Android平台基于寄存器的设计?

  1. 指令条数少,所以执行的效率更快!
  2. 数据移动次数少、临时结果存放次数少,所以更省内存和执行效率更快!
  3. 映射真实机器的寄存器,所以执行效率更快!

以上就是《为什么Android平台虚拟机基于寄存器的设计?》的全部内容,觉得本片文章有用的童鞋们记得点赞!关注!收藏!,不要吝啬你们的称赞啦啦啦啦啦

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYkVVN1y' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片