频道栏目
首页 > 程序开发 > 软件开发 > Java > 正文
Java虚拟机与Dalvik虚拟机对比使用讲解
2018-03-13 11:09:43         来源:冰鉴的博客  
收藏   我要投稿

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."":()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 "":()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          : ''
      type          : '()V'
      access        : 0x10001 (PUBLIC CONSTRUCTOR)
      code          -
      registers     : 1
      ins           : 1
      outs          : 1
      insns size    : 4 16-bit code units
00014c:                                        |[00014c] Hello.:()V
00015c: 7010 0400 0000                         |0000: invoke-direct {v0}, Ljava/lang/Object;.:()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;.:()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字节码生成指令更少,代码体积更小。

点击复制链接 与好友分享!回本站首页
上一篇:设计原则之依赖倒置原则详解
下一篇:JAVA NIO通道Channel与直接与非直接缓冲区详解
相关文章
图文推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站