Dalvik虚拟机的特点
  • 体积小,占用内存空间小
  • 专有的DEX可执行文件格式,体积更小,执行速度更快
  • 常量池采用32位索引值,寻址类方法名、字段名、常量更快
  • 基于寄存器架构,并拥有一套完整的指令系统
  • 提供了声明周期管理、堆栈管理、线程管理、安全和异常管理以及垃圾回收等功能
  • 所有的Android程序都运行在Android系统进程里,每个进程对应着一个Dalvik虚拟机实例
Java虚拟机与Dalvik虚拟机的区别
  • Java虚拟机运行字节码,Dalvik虚拟机运行Dalvik字节码
  • Dalvik可执行文件体积更小
  • Java虚拟机基于栈架构,Dalvik虚拟机基于寄存器架构
实例说明Dalvik字节码比Java字节码体积更小

如下代码

public class Hello{
    public int foo(int a, int b){
        return (a + b) * (a - b);
    }

    public static void main(String[] args){
        Hello hello = new Hello();
        System.out.println(hello.foo(5, 3));
    }
}

执行如下命令进行编译

javac Hello.java

然后执行如下命令反编译查看Java字节码

javap -c -p -classpath . Hello

javap程序位于jdk的bin目录下,说明一下各个参数

-c 对代码进行反汇编
-p 显示所有类和成员
-classpath 查找用户类文件的路径

反编译后的字节码

Compiled from "Hello.java"
public class Hello {
  public Hello();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int foo(int, int);
    Code:
       0: iload_1
       1: iload_2
       2: iadd
       3: iload_1
       4: iload_2
       5: isub
       6: imul
       7: ireturn

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class Hello
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: aload_1
      12: iconst_5
      13: iconst_3
      14: invokevirtual #5                  // Method foo:(II)I
      17: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      20: return
}

执行如下命令生成class文件对应的dex文件

./dx --dex --output=Hello.dex Hello.class

注意:dx程序位于sdk目录/build-tools/版本号/,必须将class文件拷贝到该目录再执行上面的命令,不然会出现各种莫名奇妙的问题。

接着执行如下命令看到dex文件对应的Dalvik字节码

dexdump -d Hello.dex

dexdump程序位于sdk目录/build-tools/版本号/,结果如下

Processing 'Hello.dex'...
Opened 'Hello.dex', DEX version '035'
Class #0            -
  Class descriptor  : 'LHello;'
  Access flags      : 0x0001 (PUBLIC)
  Superclass        : 'Ljava/lang/Object;'
  Interfaces        -
  Static fields     -
  Instance fields   -
  Direct methods    -
    #0              : (in LHello;)
      name          : '<init>'
      type          : '()V'
      access        : 0x10001 (PUBLIC CONSTRUCTOR)
      code          -
      registers     : 1
      ins           : 1
      outs          : 1
      insns size    : 4 16-bit code units
00014c:                                        |[00014c] Hello.<init>:()V
00015c: 7010 0400 0000                         |0000: invoke-direct {v0}, Ljava/lang/Object;.<init>:()V // method@0004
000162: 0e00                                   |0003: return-void
      catches       : (none)
      positions     :
        0x0000 line=1
      locals        :
        0x0000 - 0x0004 reg=0 this LHello;

    #1              : (in LHello;)
      name          : 'main'
      type          : '([Ljava/lang/String;)V'
      access        : 0x0009 (PUBLIC STATIC)
      code          -
      registers     : 5
      ins           : 1
      outs          : 3
      insns size    : 17 16-bit code units
000164:                                        |[000164] Hello.main:([Ljava/lang/String;)V
000174: 2200 0100                              |0000: new-instance v0, LHello; // type@0001
000178: 7010 0000 0000                         |0002: invoke-direct {v0}, LHello;.<init>:()V // method@0000
00017e: 6201 0000                              |0005: sget-object v1, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0000
000182: 1252                                   |0007: const/4 v2, #int 5 // #5
000184: 1233                                   |0008: const/4 v3, #int 3 // #3
000186: 6e30 0100 2003                         |0009: invoke-virtual {v0, v2, v3}, LHello;.foo:(II)I // method@0001
00018c: 0a00                                   |000c: move-result v0
00018e: 6e20 0300 0100                         |000d: invoke-virtual {v1, v0}, Ljava/io/PrintStream;.println:(I)V // method@0003
000194: 0e00                                   |0010: return-void
      catches       : (none)
      positions     :
        0x0000 line=7
        0x0005 line=8
        0x0010 line=9
      locals        :

  Virtual methods   -
    #0              : (in LHello;)
      name          : 'foo'
      type          : '(II)I'
      access        : 0x0001 (PUBLIC)
      code          -
      registers     : 5
      ins           : 3
      outs          : 0
      insns size    : 6 16-bit code units
000198:                                        |[000198] Hello.foo:(II)I
0001a8: 9000 0304                              |0000: add-int v0, v3, v4
0001ac: 9101 0304                              |0002: sub-int v1, v3, v4
0001b0: b210                                   |0004: mul-int/2addr v0, v1
0001b2: 0f00                                   |0005: return v0
      catches       : (none)
      positions     :
        0x0000 line=3
      locals        :
        0x0000 - 0x0006 reg=2 this LHello;

  source_file_idx   : 1 (Hello.java)

看得眼花缭乱不知所措了,没关系我们挑一部分看即可,我们来单独看看foo()这个函数在Java字节码和Dalvik字节码中的样子。

Java字节码:

public int foo(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: iadd
     3: iload_1
     4: iload_2
     5: isub
     6: imul
     7: ireturn

Dalvik字节码

[000198] Hello.foo:(II)I
0000: add-int v0, v3, v4
0002: sub-int v1, v3, v4
0004: mul-int/2addr v0, v1
0005: return v0

先从直观来看貌似也看不出同样的函数Dalvik指令占用体积小,那就来分析一波。
先看Java字节码
Java虚拟机是基于栈结构的,这里的函数用于求值,也是用到了栈结构,被称为求值栈
解释一下iload_n指令,i表示加载一个int类型的参数,load表示加载一个局部变量进栈,后面的n表示第几个局部变量,索引值从0开始计数。有人会有疑问,load_0去哪里了,看看最前面我们反编译出的完整的字节码,有0: aload_0,这里是加载了一个引用,将我们的索引值0用掉了,所以此处从1开始。
上面的代码add是加指令,sub是减指令,mul是乘指令。来画个图解释一下。

dalvik虚拟机javax dalvik虚拟机在哪一层_dalvik虚拟机javax

dalvik虚拟机javax dalvik虚拟机在哪一层_java_02

dalvik虚拟机javax dalvik虚拟机在哪一层_寄存器_03

先把两个参数入栈,遇到add指令从栈顶弹出两个参数想加后将结果入栈,然后再将两个参数入栈,遇到sub指令从栈顶弹出两个参数相减,将结果入栈,然后遇到mul指令再从栈顶弹出两个参数相乘,就是最终的计算结果。
联想一下使用栈配合后缀表达式来做四则混合运算的求值,是不是就可以完整理解上面的代码了。
上面的指令,每条指令占一个字节,所以整个foo()函数占用8个字节。

再来看看Dalvik指令
由于Dalvik虚拟机是基于寄存器的,所以这里的vn都是表示寄存器。
稍微解释一下上面的代码,将v3和v4寄存器的值相加放入v0寄存器,将v3和v4寄存器的值相减放入v1寄存器,将v0和v1寄存器的值相乘放入v0寄存器,最终将v0寄存器的值返回。
其实Dalvik虚拟机在这个过程中也用到了栈,是程序的调用栈,这个栈维护了一个寄存器列表,用于具体的操作求值。
可以看到Dalvik字节码只用了4条指令。

结论:通过对比,发现同样的函数,Dalvik字节码生成指令更少,代码体积更小。