Android里的虚拟机


前言

Android里的虚拟机, 旨在向大家简易叙述虚拟机里的一些知识。

一、虚拟机(jvm)是什么?

  • Java虚拟机(JVM)是Java Virtual Machine的缩写,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能模拟来实现的。Java虚拟机有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。
  • 引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用模式Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。

总的来说,它的作用是把平台无关的.class里的字节码翻译成平台相关的机器码,这样就实现了跨平台运行,如我们Android平台的虚拟机有Dalvik和Art虚拟机。

二、jvm和Android虚拟机

千言万语不及一张图,所以上图!
在这里插入图片描述
从图上可以清楚的分析出

1.JVM虚拟机是以java字节码加载,而Android虚拟机是基于dex加载的。

我们先来看一下class和dex的区别。
参考:Dex文件格式详解
在这里插入图片描述
jar文件里有多个class,而dex文件里只有几个数据区(每个区相当于一个list)。可以看到,当java程序编译成class后,使用dx工具将所有的class文件各个部分(成员变量,方法,常量等)整合到一个dex文件,目的是其中各个类能够共享数据,在一定程度上降低了冗余,同时也是文件结构更加经凑。dex将原来class每个文件都有的共有信息合成一体,这样减少了class的冗余。

dex和class的区别:

  • dex文件减少了整体的文件尺寸,像是一种压缩文件,一个dex文件可以表示更多的class,而class是一种简单单一的文件。
  • Android虚拟机加载类时,只需要一次IO就可以加载很多类,而class需要多次IO,dex文件提高了Android虚拟机的查找速度。
  • dex指令更加密集,class指令多。(下文会分析)
  • dex因为寄存器设计方便寻址,class是虚拟栈需要多次load和store指令(下文会分析)
  • dex适合移动设备,而class适合pc。

2.class文件存在很多冗余的信息,dex工具能把这些冗余信息去除。

int a = 10;
int b = 15;
int c = a + b;

虚拟栈(1个字节):
这段程序执行的顺序肯定是从上往下执行,可以把这段代码看作一个栈,当int a = 10;执行完就出栈了,依次按顺序出栈,且需要更多的指令(load和store),占用比较多的cpu时间。
寄存器:
可以这么理解 每段代码都会有不同的引用,这样指令可以执行的更加快速,因为毕竟虚拟栈还是一个栈,执行必须按步执行,执行效率低。但因为是引用的关系,我们肯定是要去寻址的(源地址和目标地址),所以这样就导致了指令长度就变成了2~3个字节,这就意味着需要更多的指令空间意味着数据缓冲(d-cache)更容易失效。

jvm这种没有地址(无变量申明)指令更紧凑,但完成操作需要更多的load/store指令,也意味着更多的指令分派(instruction dispatch)和内存访问次数,访问内存是执行速度的一个重要瓶颈。
Android虚拟机二地址或三地址指令虽然每条指令占的空间比较多,但总体来说可以用更少的指令完成操作,指令的分派与内存访问次数都比较少。

3.Android虚拟机基于寄存器,而jvm是基于虚拟栈的。

那栈到底是什么?程序的执行原理是什么?
我们首先需要了解同一段代码分别在Android虚拟机和JVM的运行原理。

public class Demo {
    public static void foo() {
        int a = 1;
        int b = 2;
        int c = a + b;
    }
}

我们就看这段代码好了。
我们用javap命令查看jvm里的代码。在这里插入图片描述
生成的jvm指令如下

Classfile /C:/Users/13703/Desktop/Demo.class
  Last modified 2021-1-14; size 356 bytes
  MD5 checksum c88a56df3f58652012d9e23851ab830c
  Compiled from "Demo.java"
public class Demo
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#18         // java/lang/Object."<init>":()V
   #2 = Class              #19            // Demo
   #3 = Class              #20            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               LocalVariableTable
   #9 = Utf8               this
  #10 = Utf8               LDemo;
  #11 = Utf8               foo
  #12 = Utf8               a
  #13 = Utf8               I
  #14 = Utf8               b
  #15 = Utf8               c
  #16 = Utf8               SourceFile
  #17 = Utf8               Demo.java
  #18 = NameAndType        #4:#5          // "<init>":()V
  #19 = Utf8               Demo
  #20 = Utf8               java/lang/Object
{
  public Demo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LDemo;

  public static void foo();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=0
         0: iconst_1                 ---------------------int1推至栈顶
         1: istore_0                 --------------------- 将栈顶int型数值存入本地变量(本地变量表),位置0
         2: iconst_2 				---------------------int2推至栈顶
         3: istore_1				---------------------  将栈顶int型数值存入本地变量(本地变量表),位置1
         4: iload_0					---------------------   将本地变量第0int型推送至栈顶 
         5: iload_1					---------------------   将本地变量第1int型推送至栈顶
         6: iadd					---------------------   将栈顶两int型数值相加并将结果压入栈顶
         7: istore_2				---------------------   将栈顶int型数值存入本地变量(本地变量表),位置2
         8: return					---------------------   从当前方法返回void
      LineNumberTable:
        line 3: 0
        line 4: 2
        line 5: 4
        line 6: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            2       7     0     a   I
            4       5     1     b   I
            8       1     2     c   I
}
SourceFile: "Demo.java"

所以jvm指令有很多的load/store。

我们再来生成android虚拟机里的指令。
我们需要借助Andrid sdk 里的 build-tools下的任意一个版本的dx.bat工具。
在这里插入图片描述

Demo.foo:()V:
regs: 0003; ins: 0000; outs: 0000
  0000: code-address
  0000: local-snapshot
  0000: code-address
  0000: code-address
  0000: local-snapshot
  0000: const/4 v0, #int 1 // #1     ---------------------   申明一个变量v0 (4个字节) 赋值1
  0001: local-start v0 "a": int     
  0001: const/4 v1, #int 2 // #2     ---------------------    申明一个变量v0(4个字节)赋值2
  0002: local-start v1 "b": int
  0002: add-int v2, v0, v1           ---------------------     把v0+v1赋值给v2
  0004: local-start v2 "c": int        
  0004: code-address
  0004: code-address
  0004: local-snapshot
          v0 "a": int
          v1 "b": int
          v2 "c": int
  0004: return-void					---------------------     返回空
  0005: code-address               
  debug info
    line_start: 3
    parameters_size: 0000
    0000: prologue end
    0000: line 3
    0001: line 4
    0001: +local v0 a int
    0002: line 5
    0002: +local v1 b int
    0004: line 6
    0004: +local v2 c int
    end sequence
  source file: "Demo.java"

很明显 ,我们可以从视觉的感觉到Android虚拟机里的指令(arm指令)少的多了。
后面添加了-----------------这个为有用的代码,其他都是dex工具自动给我们生成的代码。

而Android虚拟机又有分别:

  • Dalvik虚拟机
    使用JIT(Just In Time),每它实时的将一部份就把dex字节码翻译成机器码,所以安装的时候比较快,但我们每次都要编译加运行,这样会拖慢应用以后启动的效率。
  • Art虚拟机
    使用AOT(Ahead of Time),故名思意,在应用安装的期间,就把dex字节码翻译成机器码存在设备上,所以应用占用的空间会有所增大,但这样应用程序每次运行就不用重复编译了,启动就快了,从而减少了cpu的使用,改善了电池的续航。

总结

1.1.1Jvm,Dalvik与Art三者之间的区别

1.2.1 JVM虚拟机与Android虚拟机区别

Android虚拟机执行的是.dex格式文件 jvm执行的是.class文件

class文件存在很多的冗余信息,dex工具会去除冗余信息

Android虚拟机是基于寄存器的虚拟机 而jvm执行是基于虚拟栈的虚拟机

1.2.3 Art虚拟机与Dalvik虚拟机区别

  1. Dalvik下,应用每次运行都需要通过即时编译器(JIT)将字节码转换为机器码,即每次都要编译加运行,这虽然会使安装过程比较快,但是会拖慢应用以后每次启动的效率。

  2. 而在ART 环境中,应用在第一次安装的时候,字节码就会预编译(AOT)成机器码,这样的话,虽然设备和应用的首次启动(安装慢了)会变慢,但是以后每次启动执行的时候,都可以直接运行,因此运行效率会提高。

    典型的 空间换时间 128G —>apk

  3. ART占用空间比Dalvik大(字节码变为机器码之后,可能会增加10%-20%),这也是著名的“空间换时间大法"。

  4. Art预编译也可以明显改善电池续航,因为应用程序每次运行时不用重复编译了,从而减少了 CPU 的使用频率,降低了能耗。

1.2.1那dex和class到底在结构上的区别
  1. dex文件减少整体的文件尺寸 dex更像是一种压缩文件,一次可以表示更多的class。class像是一种单个文件
  2. Android虚拟机加载类时 只对dex需要一次IO可以加载很多新类,而class需要加载多次IO,Android虚拟机提高查找速度
  3. dex指令更加密集 。class指令比较多
  4. dex 寄存器设计方便寻址,class java栈需要更多次load与store指令
  5. dex适合于移动设备,性能不太高的要求。class适合PC大内存,单指令小的情况下可以快速执行
1.4.1 Android虚拟机中寄存器起什么作用,与栈的区别在哪里(又或者基于栈与基于寄存器的架构,谁更快?)

原因是:虽然没有地址(无变量声明)指令更紧凑,但完成操作需要更多的load/store指令,也意味着更多的指令分派(instruction dispatch)次数与内存访问次数;访问内存是执行速度的一个重要瓶颈,二地址或三地址指令虽然每条指令占的空间较多,但总体来说可以用更少的指令完成操作,指令分派与内存访问次数都较少。

1.5.1Arm指令究竟是什么指令,与字节码指令的区别

字节码指令 和 Arm指令内容是不一样

如 同样一个 a+b

在 jvm的指令 iadd idiv imul

但是在dalvik指令是 add-int mul-int

arm指令是由arm公司开发的。 指令含有地址,而字节码指令没有地址

字节码指令是 sun公司开发,简单高效

1.6.1为什么Art虚拟机比Dalvik虚拟机运行速度高

(1)在Dalvik下,应用每次运行都需要通过即时编译器(JIT)将字节码转换为机器码,即每次都要编译加运行,这虽然会使安装过程比较快,但是会拖慢应用以后每次启动的效率。而在ART 环境中,应用在第一次安装的时候,字节码就会预编译(AOT)成机器码,这样的话,虽然设备和应用的首次启动(安装慢了)会变慢,但是以后每次启动执行的时候,都可以直接运行,因此运行效率会提高。

(2)ART占用空间比Dalvik大(字节码变为机器码之后,可能会增加10%-20%),这也是著名的“空间换时间大法"。

(3)预编译也可以明显改善电池续航,因为应用程序每次运行时不用重复编译了,从而减少了 CPU 的使用频率,降低了能耗。

ARM指令集

系统性能的显著提升

应用启动更快、运行更快、体验更流畅、触感反馈更及时

更长的电池续航能力

支持更低的硬件

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值