VM之从字节码看vm的实现差异

1.基于栈实现,以JVM为例

java源文件

执行命令:得到字节码信息

从上面可以看到main方法由8条指令组成,一般称他为opcode,注意这个opcode并不是汇编语言,而是jvm可以识别的语言,可以说汇编之于Native等于opcode之于jvm

执行分析:

iconst_1 :将1放入操作数栈顶部(此时操作数栈深度为1)

istore_1: 从栈顶取出值,赋值给变量1 (此时操作数栈深度为0)

iconst_2 :将2放入操作数栈顶部(此时操作数栈深度为1)

istore_2:从栈顶取出值,赋值给变量2 (此时操作数栈深度为0)

iload_1,iload_2: 从变量1和变量2加载值,放到操作数栈( 此时操作数栈深度为2)

iadd:把栈顶的两个int值取出并且相加,结果放在栈顶 (此时操作数栈深度为1)

istore_3:取出栈顶数据,赋值给变量3

return:返回

基本形式 opcode + oprand + oprand …

通过字节码可以看到,opcode的操作不论是源数据还是结果数据(统称为oprand)都保存在操作数栈(oprand stack)里,而使用的最大深度成为操作数栈大小( oprand stack size),操作数栈大小和变量的大小是在编译过程就能够缺确定的,dump信息中的”stack=2, locals=4, args_size=1″,stack就是表示操作数栈的大小,从而让方法在invok的之前可以预先分配好操作数栈的深度,除此之外locals表示方法的局部变量数量(包含输入参数), args_size 为输入参数的数量

对于这种opcode的操作是基于操作数栈的虚拟机被称为基于栈实现的虚拟机,这种虚拟机由于不依赖寄存器,可移植性强,但是由于指令比较多,导致编译后的字节码文件较大

jvm栈帧和操作数栈与局部变量表的关系

可以看到操作数栈和局部变量表是由栈帧里的内存分配的

1.基于寄存器实现,以DVM为例

dvm的字节码与jvm不同,jvm是可以一个一个类放在不同的class文件的,而dex是多个class文件合并的,同样的,jvm的class常量池是每个class都不一样的,而dex文件的常量池是多个类合并一起使用的,这也是为了让移动端apk占用更小的磁盘而进行的优化操作

同样的,我们使用一样的代码

java源文件

使用dexdump查看字节码:adb shell dexdump -h -d classes.dex

可以看到 dvm的字节码的只有4条,比起jvm的字节码指令条数少了整整两倍

const/4 v0, #int 1:表示以四字节的方法把1赋值给寄存器0

const/4 v1, #int 2 表示以四字节的方式把1赋值给寄存器1

add-int v2, v0, v1: 把寄存器0和寄存器1上的值相加,结果放到寄存器2中

return-void :返回空

基本形式 opcode + oprand + oprand …

从上面可以看到,dvm的字节码所有的opcode操作单内容和结果(同样称为 oprand )都是放到寄存器中,dvm的字节码的dump信息中可以看到每个方法的registers使用数量,也就是这个方法最多使用多少个寄存器,使用的寄存器数量与传入参数和局部变量多少有关,一般来说寄存器数量=局部变量表数量+传入参数数量,上图ins=1表示输入参数1个,outs表示call的函数最多有几个参数这里没call其他函数所有是0,registers =4,表示这个方法最多占用4个寄存器

对于这种opcode操作是基于寄存器的虚拟机就称为基于寄存器实现的的虚拟机。与基于栈实现的不同,基于寄存器实现的虚拟机没有操作数栈这个概念,因此在dump信息中看不到操作数栈的大小,基于寄存器实现的虚拟机他的寄存器可能是虚拟的(如dalvik实现,寄存器是放在栈帧里的),对于完全使用虚拟寄存器的的vm,跨平台性我并不认为比基于栈的弱

dalvk的栈帧和寄存器的关系

图中交叉部分表示,前一个函数的输出参数等于下一个函数的输入参数,从图中可以看出,dalvik实现中,寄存器由栈模拟