前言

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指令集

      系统性能的显著提升

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

      更长的电池续航能力

      支持更低的硬件

      转载: https://blog.csdn.net/sunlifeall/article/details/112489978

      点击阅读全文
      Logo

      华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

      更多推荐