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是乘指令。来画个图解释一下。
先把两个参数入栈,遇到add指令从栈顶弹出两个参数想加后将结果入栈,然后再将两个参数入栈,遇到sub指令从栈顶弹出两个参数相减,将结果入栈,然后遇到mul指令再从栈顶弹出两个参数相乘,就是最终的计算结果。
联想一下使用栈配合后缀表达式来做四则混合运算的求值,是不是就可以完整理解上面的代码了。
上面的指令,每条指令占一个字节,所以整个foo()函数占用8个字节。
再来看看Dalvik指令
由于Dalvik虚拟机是基于寄存器的,所以这里的vn都是表示寄存器。
稍微解释一下上面的代码,将v3和v4寄存器的值相加放入v0寄存器,将v3和v4寄存器的值相减放入v1寄存器,将v0和v1寄存器的值相乘放入v0寄存器,最终将v0寄存器的值返回。
其实Dalvik虚拟机在这个过程中也用到了栈,是程序的调用栈,这个栈维护了一个寄存器列表,用于具体的操作求值。
可以看到Dalvik字节码只用了4条指令。
结论:通过对比,发现同样的函数,Dalvik字节码生成指令更少,代码体积更小。