Dalvik虚拟机浅识
[http://www.linuxidc.com/Linux/2011-09/43812.htm]这篇文章内容大部分来自一年前的一篇调研报告,加上对dalvik虚拟机的一些认识,匆忙整理出来供大家参考。如有不对的地方请不吝指出。I.什么是Dalvik虚拟机?II.DalvikVM与JVM有什么区别?III.DalvikVM有什么新的特点?IV
[http://www.linuxidc.com/Linux/2011-09/43812.htm]
这篇文章内容大部分来自一年前的一篇调研报告,加上对dalvik虚拟机的一些认识,匆忙整理出来供大家参考。如有不对的地方请不吝指出。
I.什么是Dalvik虚拟机?
II.DalvikVM与JVM有什么区别?
III.DalvikVM有什么新的特点?
IV.DalvikVM的架构是怎么样的?
n?111111111111111111
什么是Dalvik虚拟机?没有人给出过一个明确的定义,但是,我们似乎可以从人们对Java虚拟机的描述中得到些信息。
Java虚拟机(JVM)是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。它有自己完善的硬件架构(如处理器、堆栈、寄存器等),还具有相应的指令系统。使用“Java虚拟机”程序就是为了支持与操作系统无关、在任何系统中都可以运行的程序。
因此,我们不妨对Dalvik虚拟机作出这样的描述:
Dalvik虚拟机是Android程序的虚拟机,是Android中Java程序的运行基础。其指令集基于寄存器架构,执行其特有的文件格式——dex字节码来完成对象生命周期管理、堆栈管理、线程管理、安全异常管理、垃圾回收等重要功能。它的核心内容是实现库(libdvm.so),架构由C语言实现。依赖于Linux内核的一部分功能——线程机制、内存管理机制,能高效使用内存,并在低速CPU上表现出的高性能。每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行。
n?2222222222222222222222222222222222
然而:DalvikVM ≠Java VMI.dalvik基于寄存器,而JVM基于stack II.Dalvik执行的是特有的DEX文件格式,而JVM运行的是*.class文件格式。
优势:1、在编译时提前优化代码而不是等到运行时
2、虚拟机很小,使用的空间也小;被设计来 满足可高效运行多种虚拟机实例。
3、常量池已被修改为只使用32位的索引,以简化解释器
JVM的字节码主要是零地址形式的,概念上说JVM是基于栈的架构。Google Android平台上的应用程序的主要开发语言是Java,通过其中的DalvikVM来运行Java程序。为了能正确实现语义,DalvikVM的许多设计都考虑到与JVM的兼容性;但它却采用了基于寄存器(见补充1)的架构,其字节码主要是二地址/三地址的混合形式。
基于栈与基于寄存器的架构,谁更快?现在实际的处理器,大多都是基于寄存器的架构,从侧面反映出基于寄存器比基于栈的架构更与实际的处理器接近。但对于VM来说,源架构的求值栈或者寄存器都可能是用实际机器的内存来模拟的,所以性能特性与实际硬件又有不同。一般认为基于寄存器架构的DalvikVM比基于栈架构JVM执行效率更高,原因是:虽然零地址指令更紧凑,但完成操作需要更多的load/store指令,也意味着更多的指令分派(instruction dispatch)次数与内存访问次数;访问内存是执行速度的一个重要瓶颈,二地址或三地址指令虽然每条指令占的空间较多,但总体来说可以用更少的指令完成操作,指令分派与内存访问次数都较少。
我们从下面的截图可以明了的看到与同一段Java代码对应的Java bytecode 与Dalvid bytecode的比较。(摘自网络)
??
n33333333333333333333333333333333333
专有的DEX文件格式
一个应用中会定义很多类,
编译完成后即会有很多相应
的CLASS文件,CLASS文件
间会有不少冗余的信息。
udex字节码和标准Java的字节码(Class)在结构上的一个区别是dex字节码将多个文件整合成一个,这样,除了减少整体的文件尺寸,I/O操作,也提高了类的查找速度。
u原来每个类文件中的常量池现在由DEX文件中一个常量池来管理。
uDEX文件可以进行进一步优化。优化主要是针对以下几个方面:
1、调整所有字段的字节序(LITTLE_ENDIAN)和对齐结构中的没一个域
2、验证DEX文件中的所有类
3、对一些特定的类进行优化,对方法里的操作码进行优化,优化优化后的文件大小会有所增加,应该是原DEX文件的1-4倍。odex是为了在运行过程中进一步提高性能,对dex文件的进一步优化, 这个步骤在安装程序的时候可以看到。
DEX文件的生成:
Android系统和Dalvik虚拟机提供了工具(DX) 在把Java源代码编译成CLASS文件后 使用DX工具(见补充2) DEX文件的结构相对于.jar更加紧凑 但是为了获得高效率我们还得进一步对.dex进行优化。
n444444444444444444444444444
一个应用,一个虚拟机实例,一个进程!!!
u每一个Android应用都运行在一个Dalvik虚拟机实例里,而每一个虚拟机实例都是一个独立的进程空间。每个进程之间可以通信(IPC,Binder机制实现)。虚拟机的线程机制,内存分配和管理,Mutex等等都是依赖底层操作系统而实现的。
u 不同的应用在不同的进程空间里运行,当一个虚拟机关闭或意外中止时不会对其它虚拟机造成影响,可以最大程度的保护应用的安全和独立运行。
u Zygote是虚拟机实例的孵化器。AndroidRuntime.cpp中ZygoteInit.main()的执行会完成一个分裂,分裂出来的子进程继续初始化Java层的架构,这个分裂出来的进程就是system_server。每当系统要求执行一个Android应用程序,Zygote就会FORK出一个子进程来执行该应用程序。这样做的好处显而易见:Zygote进程是在系统启动时产生的,它会完成虚拟机的初始化,库的加载,预置类库的加载和初始化等等操作,而在系统需要一个新的虚拟机实例时,Zygote通过复制自身,最快速的提供个系统。另外,对于一些只读的系统库,所有虚拟机实例都和Zygote共享一块内存区域,大大节省了内存开销。(下图借签网络资源)
nDalvikVM 处在Android系统架构中间件的位置。即处在Linux内核层(或在其两层中抽象出一个HAL层)与应用架构层之间。
nDalvikVM依赖于Linux内核来完成某些功能,如:线程管理、底层内存管理。
nDalvikVM体现了Android系统的一个显著特点:模块化以及各模块间的低偶合度。这体现于VM以及类库内部的高度模块化,这继承了ApacheHarmony的优点。
想了解dalvik虚拟机在android系统的位置,不得不贴上一张经典的结构图:
通过以上的介绍,我们对DalvikVM能实现的功能以及为什么需要这么一个虚拟机有了初步的认识。观其内部,AndroidVM的架构是什么样子的呢?
在源码中,DalvikVM相关的内容在Android中是一个独立的代码路径:dalvik/,其中包含了目标机和主机的内容。其主要的目录如下所示:
pDalvik/dx目录的内容是dex工具库,其最终将生成静态库libdex.a。
pDalvik/vm目录的内容是虚拟机的实现库,其最终将生成libdvm.so(Dalvik虚拟机实现核心库)。
pDavlik/dalvikvm目录中的内容是虚拟机的可执行程序。
pLibdvm.so连接静态库libdex.a,虚拟机的可执行程序为dalvik,它将连接libdvm.so。
Dalvik/
|--Dx
|--Vm
| |--Arch (JNICallbridge)
| --Mterp (解释器实现)
| --Native
| --Compiler (JIT编译实现)
|--Dalvikvm (Dalvik虚拟机入口函数)
(JNI见补充3)
n666666666666666666666666666666666666666
解释器是Dalvik虚拟机的执行引擎,它负责解释执行dex字节码。为了提高运行效率,Google为ARM用汇编重写了解释器,我们称之为快速型解释器。从虚拟机测试程序caffeinmark可以看出性能是有了明显的提升。
Android2.2针对解释器新增了14条解释器指令(dex字节码多了14种格式),因此,在2.2上编译出来的*.apk是无法向上兼容的。
Google从Android2.2开始在dalvik虚拟机中引入了JIT这个机制,Android2.3在Android2.2上对JIT作了进一步的优化。对JIT的分析敬请期待~嘿嘿~
Dalvik虚拟机简介补充
1 dalvik基于寄存器
这个问题体现在虚拟机底层对字节码的处理方面。Dex字节码是dalvik虚拟机能直接解释执行的最小单位,它的指令格式与汇编类似,有第一操作寄存器,第二操作寄存器,立即数等。如:ppt中的出现的一条字节码add-int/2addr v0,v1。这里的v表示的是虚拟机中的寄存器,是为了区别于r而命名的。要注意的是,CPU上是没有两个寄存器与v0、v1与之对应的。但为什么我们又说它是基于寄存器的呢?
问题的关键在于这条字节码在汇编级是怎么实现的,如add-int/2addr的实现:
mov r9, rINST, lsr #8 @r9<- A+
mov r3, rINST, lsr #12 @r3<- B
and r9, r9, #15
GET_VREG(r1, r3) @ r1<- vB
GET_VREG(r0, r9) @ r0<- vA
FETCH_ADVANCE_INST(1) @advance rPC, load rINST
add r0, r0, r1
GET_INST_OPCODE(ip) @ extract opcode from rINST
SET_VREG($result, r9) @vAA<- $result
GOTO_OPCODE(ip) @ jump to next instruction
#defineGET_VREG(_reg, _vreg) ldr _reg, [rFP, _vreg, lsl #2]
#defineFETCH_ADVANCE_INST(_count) ldrh rINST, [rPC, #(_count*2)]!
#defineGET_INST_OPCODE(_reg) and _reg, rINST, #255
#defineSET_VREG(_reg, _vreg) str _reg, [rFP, _vreg, lsl #2]
#defineGOTO_OPCODE(_reg) add pc, rIBASE, _reg, lsl#${handler_size_bits}
所谓的基于寄存器可以从两方面理解:1、字节码的指令格式带虚拟寄存器,在字节码这个层面,它可以直接操作虚拟寄存器(对应内存)。2、相对于基于栈的Java虚拟机,目标板CPU完成操作需要更多的load/store指令(入栈出栈操作),而基于寄存器的dalvik虚拟机,从上面一个解释器例程可以看出解释一条字节码所需load/store指令数下降,更多的是配合其它汇编指令完成。
2 dx工具使用及dex字节码理解
dx工具可以单独剥离出来使用。如,我们在pc上用java编译器把java源码编译成class字节码(这个步骤也可以在Linux服务器上完成),然后在linux上直接使用dx把该class字节码变成dex字节码。因此我们可以认为google为java包装了一种新的中间格式,然后dalvik虚拟机对这种中间格式进行解释执行或编译运行。
%echo 'class Foo {'\
>'public static void main(String[]args) {'\
>'System.out.println("Hello,world"); }}' > Foo.java
%javac Foo.java
% dx --dex --output=foo.jar Foo.class
%adb push foo.jar /sdcard
%adb shell dalvikvm -cp /sdcard/foo.jarFoo
Hello,world
3 dalvik虚拟机处理JNI
JNI(JavaNative Interface)中文为JAVA本地方法,即允许Javacode在JVM内运行像C、C++、Assembly等语言。JNI的最重要的好处是,对底层Java虚拟机执行没有任何限制。当一个应用不能完全靠Java语言实现的时候,我们可以使用JNI通过Javanative methods来处理这种情况。
JNI的整个架构及实现也是位于dalvik虚拟机,它绝大部分实现都是C,只有一个部分与CPU架构相关,它必须用相关的汇编来实现,即JNICallbridge。JNICallbridge的任务有两个,一是将java函数中的参数正确的传递到本地方法函数中;二是将函数的返回值正解的返回。参数的传递规则因CPU而异。如ARM,参数从左至右,r0~r3接受前4个参数,剩下的参数依次入栈。
更多推荐
所有评论(0)