Java 虚拟机与 Dalvik 虚拟机的区别

本文对比了Java虚拟机(JVM)的基于栈架构与Dalvik虚拟机(DVM)的基于寄存器架构。通过具体示例分析了两种架构在指令集、数据存取方式及执行效率上的差异。

简述

JVM基于栈架构
DVM 虚拟机基于寄存器架构(意指由一个指令之输出或输入可以直接索引到的寄存器组群)。
因为实现架构的差异,则DVM对指令的响应要快于 JVM。

代码实践

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));
    }

}

编写以上代码保存为Hello.java。打开命令提示符,执行命令javac Hello.java编译生成Hello.class文件。
然后执行命令dx --dex --out=Hello.dex Hello.class
不幸的是,它抛出异常了,看来是 JDK的版本问题造成的。

UNEXPECTED TOP-LEVEL EXCEPTION:
java.lang.RuntimeException: Exception parsing classes
    at com.android.dx.command.dexer.Main.processClass(Main.java:752)
    at com.android.dx.command.dexer.Main.processFileBytes(Main.java:718)
    at com.android.dx.command.dexer.Main.access$1200(Main.java:85)
    at com.android.dx.command.dexer.Main$FileBytesConsumer.processFileBytes(Main.java:1645)
    at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:170)
    at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)
    at com.android.dx.command.dexer.Main.processOne(Main.java:672)
    at com.android.dx.command.dexer.Main.processAllFiles(Main.java:574)
    at com.android.dx.command.dexer.Main.runMonoDex(Main.java:311)
    at com.android.dx.command.dexer.Main.run(Main.java:277)
    at com.android.dx.command.dexer.Main.main(Main.java:245)
    at com.android.dx.command.Main.main(Main.java:106)
Caused by: com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)
    at com.android.dx.cf.direct.DirectClassFile.parse0(DirectClassFile.java:472)
    at com.android.dx.cf.direct.DirectClassFile.parse(DirectClassFile.java:406)
    at com.android.dx.cf.direct.DirectClassFile.parseToInterfacesIfNecessary(DirectClassFile.java:388)
    at com.android.dx.cf.direct.DirectClassFile.getMagic(DirectClassFile.java:251)
    at com.android.dx.command.dexer.Main.parseClass(Main.java:764)
    at com.android.dx.command.dexer.Main.access$1500(Main.java:85)
    at com.android.dx.command.dexer.Main$ClassParserTask.call(Main.java:1684)
    at com.android.dx.command.dexer.Main.processClass(Main.java:749)
    ... 11 more
1 error; aborting
$ java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

这里需要把JDK版本指定为1.6重新编译Hello.class(为什么要更换成1.6?)

$ javac -source 1.6 -target 1.6 Hello.java 

然后执行命令dx --dex --out=Hello.dex Hello.class。此刻,已经顺利生成了Hello.dex
接下来执行

    javap -c -classpath . Hello

命令执行后得到如下代码,针对foo()函数:

    // 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

接着执行命令:dexdump可在 sdk/build-tools/目录下找到。

dexdump -d Hello.dex

生成 Dalvik字节码如下,针对foo()函数:

    // Dalvik 字节码
    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

查看上面的 Java 字节码,共8条指令(每条指令占一个字节,并且这些指令都没有参数)。
Q:那么这些指令是如何存取数据的呢?
A:Java 指令集被称为零地址形式的指令集,所谓零地址形式,是指指令的源参数与目标参数都是隐含的,它通过 Java 虚拟机中提供的一种数据结构“求值栈”来传递的。
结合代码来理解理论知识:

完整指令PC类型指令连接符局部变量步骤说明
0:iload_10i
(int类型)
load
(将局部变量存入 Java 栈)
-1
(局部变量索引)
将第2个 int 类型的局部变量进栈
1:iload_21iload-2将第3个 int 类型的局部变量进栈
2:iadd2iadd从栈顶弹出两个 int 类型值,将值相加,然后把结果压回栈顶
3:iload_13iload-1将第2个 int 类型的局部变量进栈
4:iload_24iload-2将第3个 int 类型的局部变量进栈
5:isub5isub从栈顶弹出两个 int 类型值,将值相减,然后把结果压回栈顶
6:imul6imul从栈顶弹出两个 int 类型值,将值相乘,然后把结果压回栈顶
7:ireturn7ireturn返回一个 int 值

所以与之类似的还有lloaddloadfload… 更多信息查看:Java字节码


Java 虚拟机与 Dalvik 虚拟机的区别
相比 Java 字节码,我们依旧对照代码分析 Dalvik 字节码。


比起 JVM字节码显然 Dalvik字节码更简洁,只有4条指令就完成了上面的工作。

第一条指令 add-int 将 v3与 v4寄存器的值相加,然后保存到 v0寄存器。整个指令的操作中使用到了三个参数,v3、v4分别代表 foo()函数的入参。
第二条指令 sub-int 将 v3减去 v4的值保存到 v1寄存器。
第三条指令 mul-int/2addr 将 v0乘以 v1保存到 v0寄存器。
第四条指令返回 v0寄存器的值。

Dalvik虚拟机运行时同样为每个线程维护一个 PC计数器与调用栈,与 Java 虚拟机不同的是,这个调用栈维护一份寄存器列表,寄存器的数量在方法结构体的 registers 字段中给出,Dalvik 迅疾会根据这个值来创建一份虚拟的寄存器列表。

通过分析得出,基于寄存器架构的 Dalvik 虚拟机与基于栈架构的 Java 虚拟机相比,由于生产的代码指令减少了,程序执行的速度会更快一些。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值