【转】安卓Java的虚拟机区别
Google于2007年底正式发布了, 作为 Android系统的重要特性,Dalvik虚拟机也第⼀次进⼊了⼈们的视野。它对内存的⾼效使⽤,和在低速CPU上表现出的⾼性能,确实令⼈刮⽬相看。依赖于底层Posix兼容的操作系统,它可以简单的完成进程隔离和线程管理。每⼀个Android 应⽤在底层都会对应⼀个独⽴的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执⾏。
很多⼈认为Dalvik虚拟机是⼀个Java虚拟机,因为Android的编程语⾔恰恰就是Java语⾔。但是这种说法并不准确,因为Dalvik虚拟机并不是按照Java虚拟机的规范来实现的,两者并不兼容;同时还要两个明显的不同:
Java虚拟机运⾏的是Java字节码,⽽Dalvik虚拟机运⾏的则是其专有的⽂件格式DEX(Dalvik Executable)。
在Java SE程序中的Java类会被编译成⼀个或者多个字节码⽂件(.class)然后打包到JAR⽂件,⽽后Java虚拟机会从相应的CLASS ⽂件和JAR⽂件中获取相应的字节码;Android应⽤虽然也是使⽤Java语⾔进⾏编程,但是在编译成CLASS⽂件后,还会通过⼀个⼯具(dx)将应⽤所有的 CLASS⽂件转换成⼀个DEX⽂件,⽽后Dalvik虚拟机会从其中读取指令和数据。
Dalvik和Android系统Android作为新⼀代的基于Linux的开源⼿机操作系统,其系统架构由下⽽上可以分为以下⼏部分:Linux内核
本地库
Android运⾏库
应⽤框架
应⽤
java虚拟机和Dalvik虚拟机的区别:
java虚拟机Dalvik虚拟机
java虚拟机基于栈。基于栈的机器
必须使⽤指令来载⼊和操作栈上数
据,所需指令更多更多
dalvik虚拟机是基于寄存器的
java虚拟机运⾏的是java字节码。(java类会被编译成⼀个或多个字节码.class⽂件,打包到.jar⽂件中,java虚拟机从相应的.class⽂件和.jar⽂件中获取相应的字节码)Dalvik运⾏的是⾃定义的.dex字节码格式。(java类被编译成.class⽂件后,会通过⼀个dx⼯具将所有的.class⽂件转换成⼀个.dex⽂件,然后dalvik虚拟机会从其中读取指令和数据)
常量池已被修改为只使⽤32位的索引,以简化解释器。dalvik的堆和栈的参数可以通过-Xms和-Xmx更改⼀个应⽤,⼀个虚拟机实例,⼀个进程(所有android应⽤的线程都是对应⼀个linux线程,都运⾏在⾃⼰的沙盒中,不同的应⽤在不同的进程中运⾏。每个android dalvik应⽤程序都被赋予了⼀个独⽴的linux PID(app_*))
Dalvik和标准Java虚拟机(JVM)之间的⾸要差别之⼀,就是Dalvik基于寄存器,⽽JVM基于栈。
Dalvik和Java之间的另外⼀⼤区别就是运⾏环境——Dalvik经过优化,允许在有限的内存中同时运⾏多个虚拟机的实例,并且每⼀个 Dalvik 应⽤作为⼀个独⽴的Linux进程执⾏。
(1)虚拟机很⼩,使⽤的空间也⼩;
(2)Dalvik没有JIT编译器;
(3)常量池已被修改为只使⽤32位的索引,以简化解释器;
(4)它使⽤⾃⼰的字节码,⽽⾮Java字节码。
Dalvik虚拟机架构:
在android源码中,Dalvik虚拟机的实现位于“dalvik/”⽬录下,其中“dalvik/vm”是虚拟机的实现部分,将会编译成libdvm.so;
⽽"dalvik/libdex"将会编译成libdex.a静态库作为dex⼯具;“dalvik/dexdump”是.dex⽂件的反编译⼯具;虚拟机的可执⾏程序位
于“dalvik/dalvikvm”中,将会编译成dalvikvm可执⾏⽂件。
dalvik虚拟机架构:
Android应⽤编译及运⾏流程:
Dalvik进程管理:
dalvik进程管理是依赖于linux的进程体系结构的,如要为应⽤程序创建⼀个进程,它会使⽤linux的fork机制来复制⼀个进程(复制进程往往⽐创建进程效率更⾼)。
Zygote是⼀个虚拟机进程,同时也是⼀个虚拟机实例的孵化器,它通过init进程启动。⾸先会孵化出System_Server(android绝⼤多系统服务的守护进程,它会监听socket等待请求命令,当有⼀个应⽤程序启动时,就会向它发出请求,zygote就会FORK出⼀个新的应⽤程序进程).每当系统要求执⾏⼀个android应⽤程序时,Zygote就会运⽤linux的FORK进制产⽣⼀个⼦进程来执⾏该应⽤程序。
JVM和Dalvik进程管理:
linux中进程间通信的⽅式有很多,但是dalvik使⽤的是信号⽅式来完成进程间通信。
Android的初始化流程:
实际上.dex⽂件就是把多个class⽂件中的常量、⽅法等放到⼀起。形成如上图所⽰的结构。
在架构上jvm是基于栈的架构,所以每次访问数据cpu都要到内存中取到数据。
⽽dalvik是基于寄存器的架构。寄存器是在cpu上的⼀块存储空间,cpu如果直接从寄存器上读取数据的话就会快很多。
【以下也是转】
(1) Dalvik VM和JVM 的第⼀个区别是 Dalvik VM是基于寄存器的架构(reg based),⽽JVM是栈机(stack based)。reg based VM的好处是可以做到更好的提前优化(ahead-of-time optimization)。另外reg based的VM执⾏起来更快,但是代价是更⼤的代码长度。
(2) 另外⼀个区别是Dalvik可以允许多个instance 运⾏,也就是说每⼀个Android 的App是独⽴跑在⼀个VM中.这样做的好处是⼀个App crash 只会影响到⾃⾝的VM,不会影响到其他。 Dalvik的设计是每⼀个Dalvik的VM都是Linux下⾯的⼀个进程。那么这就需要⾼效的IPC。另外每⼀个VM是单独运⾏的好处还有可以动态active/deactive⾃⼰的VM⽽不会影响到其他VM
(3) 接下来就是关于版权之类争论。(可以参看下⾯⽂章)
既然reg based VM有那么多好处,为什么之前设计JAVA的⼈没有采⽤reg based⽽是采⽤stack based的呢?原来stack based的VM也有其优点,就是它不对host平台的reg数量做假设,有利于移植到不同的平台。⽽Dalvik则不关⼼这些,因为它本来就是为ARM这样的多reg平台设计的。另外Dalvik被移植到x86也说明,即使是x86这种reg很少的平台,reg based的VM也是没有问题的。
下⾯着重说下DVM的优势:(部分⽂字我加⿊以突出)
1、在编译时提前优化代码⽽不是等到运⾏时
2、虚拟机很⼩,使⽤的空间也⼩;被设计来满⾜可⾼效运⾏多种虚拟机实例。
3、常量池已被修改为只使⽤32位的索引,以简化解释器
JVM 的字节码主要是零地址形式的,概念上说JVM是基于栈的架构。Google Android平台上的应⽤程序的主要开发语⾔是Java,通过其中的Dalvik VM来运⾏Java程序。为了能正确实现语义,Dalvik VM的许多设计都考虑到与JVM的兼容性;但它却采⽤了基于寄存器的架构,其字节码主要是⼆地址/三地址的混合形式。
基于栈与基于寄存器的架构,谁更快?现在实际的处理器,⼤多都是基于寄存器的架构,从侧⾯反映出基于寄存器⽐基于栈的架构更与实际的处理器接近。但对于VM来说,源架构的求值栈或者寄存器都可
能是⽤实际机器的内存来模拟的,所以性能特性与实际硬件⼜有不同。⼀般认为基于寄存器架构的Dalvik VM⽐基于栈架构JVM执⾏效率更⾼,原因是:虽然零地址指令更紧凑,但完成操作需要更多的load/store指令,也意味着更多的指令分派(instruction dispatch)次数与内存访问次数;访问内存是执⾏速度的⼀个重要瓶颈,⼆地址或三地址指令虽然每条指令占的空间较多,但总体来说可以⽤更少的指令完成操作,指令分派与内存访问次数都较少。
我们从下⾯的截图可以明了的看到与同⼀段Java代码对应的Java bytecode 与Dalvid bytecode的⽐较:⽹上⼀些⽂章在讨论 Dalvik 时,⼤都简单提及 Dalvik 执⾏速度⽐ JVM 快,但移植性稍差。这⾥我们延伸探讨⼀下。在⼀个解释器上执⾏ VM 指令,包含三个步骤,指令分派、访问操作数和执⾏计算。指令分派(Instructions dispatch)负责从内存中读取 VM 指令,然后跳转到相应的解释器代码指令分派中。上⾯提到过,完成同样的事情,基于栈的虚拟机需要更多的指令,意味着更多的指令分派和内存访问次数,这是 JVM 的执⾏性能不如Dalvik VM 的原因之⼀。
访问操作数访问操作数(Operands access)是指读取和写回源操作数和⽬的操作数。Dalvik VM 通过虚拟操作数寄存器来访问操作数,由于具有相近的⾎缘, Dalvik 的虚拟寄存器在映射到物理寄存器⽅⾯具有更充分的优势,这也是 Dalvik VM 性能较佳的⼀个原因。 JVM 的操作数通过操作数栈来访问,⽽因为指令中没有使⽤任何通⽤寄存器,在虚拟机的实现中可以⽐较⾃由的分配实际机器的寄存器,因⽽可移植性⾼。作为⼀个优化,操作数栈也可以由编译器映射到物理寄存器上,减少数据移动的开销。指
令执⾏(Instructions compute)这个似乎没什么可解释的,⽼⽼实实执⾏就⾏。指令执⾏
⼀个应⽤中会定义很多类,编译完成后即会有很多相应的CLASS⽂件,CLASS⽂件间会有不少冗余的信息。
dex字节码和标准Java的字节码(Class)在结构上的⼀个区别是dex字节码将多个⽂件整合成⼀个,这样,除了减少整体的⽂件尺⼨,I/O操作,也提⾼了类的查速度。
原来每个类⽂件中的常量池现在由DEX⽂件中⼀个常量池来管理。
DEX⽂件可以进⾏进⼀步优化。优化主要是针对以下⼏个⽅⾯:
1、调整所有字段的字节序(LITTLE_ENDIAN)和对齐结构中的没⼀个域
安卓进程间通信2、验证DEX⽂件中的所有类
3、对⼀些特定的类进⾏优化,对⽅法⾥的操作码进⾏优化
优化优化后的⽂件⼤⼩会有所增加,应该是原DEX⽂件的1-4倍。 odex是为了在运⾏过程中进⼀步提⾼性能,对dex⽂件的进⼀步优化
每⼀个Android应⽤都运⾏在⼀个Dalvik虚拟机实例⾥,⽽每⼀个虚拟机实例都是⼀个独⽴的进程空间。每个进程之间可以通信
(IPC,Binder机制实现)。虚拟机的线程机制,内存分配和管理,Mutex等等都是依赖底层操作系统⽽实现的。
不同的应⽤在不同的进程空间⾥运⾏,当⼀个虚拟机关闭或意外中⽌时不会对其它虚拟机造成影响,可以最⼤程度的保护应⽤的安全和独⽴运⾏。
Zygote是虚拟机实例的孵化器。AndroidRuntime.cpp中ZygoteInit.main()的执⾏会完成⼀个分裂,分裂出来的⼦进程继续初始化Java层的架构,这个分裂出来的进程就是system_server。每当系统要求执⾏⼀个Android应⽤程序,Zygote就会FORK出⼀个⼦进程来执⾏该应⽤程序。这样做的好处显⽽易见:Zygote进程是在系统启动时产⽣的,它会完成虚拟机的初始化,库的加载,预置类库的加载和初始化等等操作,⽽在系统需要⼀个新的虚拟机实例时,Zygote通过复制⾃⾝,最快速的提供个系统。另外,对于⼀些只读的系统库,所有虚拟机实例都和Zygote共享⼀块内存区域,⼤⼤节省了内存开销。
=============================分割线===========================
下⾯我以我的认知来简单总结描述⼀下,DVM和JVM这种架构上的差异所产⽣的影响
JVM其核⼼⽬的,是为了构建⼀个真正跨OS平台,跨指令集的程序运⾏环境(VM)。DVM的⽬的是为了将android OS的本地资源和环境,以⼀种统⼀的界⾯提供给应⽤程序开发。严格来说,DVM不是真正的VM,它只是开发的时候提供了VM的环境,并不是在运⾏的时候提供真正的VM容器。这也是为什么JVM必须设计成stack-based的原因。
JVM:所有的jar程序,其运⾏环境完全是由JVM来提供,包括运⾏时,各类资源的调度,⽽JVM的架构,其设计为⼀个JVM⾥⾯可以运⾏多个java程序,JVM就像⼀个真正的“机器”,可以跑着多个程序。如果去看看⼀些企业级的JVM(例如tom cat,WAS),从OS的进程管理中,⼀般你只能看见⼀个JVM的进程(当然,你也可以起多个JVM,但JVM架构就是OS-JVM-APP的3层运⾏时模式),⽽看不见JVM⾥⾯运⾏的程序,⽽⼀个JVM⾥,可以跑多个java app。简单得说,JVM完全屏蔽了应⽤程序和OS之间的联系,⽽改⽤JVM充当了中间层,这也是⼀个真正跨平台运⾏时VM必须要做到的。只要是相同的JDK,JVM为所有在其中运⾏的程序,提供了完全⼀致的运⾏环境,⽽不论你是什么样的底层OS和硬件条件。因此这也是我在其他⼀篇答案中提到,JVM的特点是取底层OS和硬件环境的交集,从⽽保障这种⼀致性。⽽所有应⽤程序和底层资源的互动,⼀定是依赖JVM的传递和转换来实现。JVM真正实现了⼀个OS对应⽤程序运⾏时管理的所有功能。从开发环境⾓度和运⾏时⾓度,都是完全⼀致的真正VM
DVM:⽽DVM的特点在于使⽤了Zygote,Zygote有⼏个⾮常有意思的特点。
⼀是Zygote采⽤预加载,由其⾸先判定安装的APK的需要以及相互依存树,以及OS及硬件环境的特点,在每次启动的时候进⾏预加载(现在你明⽩为什么android的app在应⽤管理⾥你能轻易查到它都调⽤了那些关键性的本地资源的原因了吧?),这就意味着,你安装的应⽤越多,Zygote的加载就越慢,⼀般来说你的⼿机启动就会越慢。另外来说,在不同的硬件环境⾥(例如有⽆GPS芯⽚)Zygote初始化的实例是不同的。也就是说,zygote并不提供⼀个统⼀的运⾏环境,具有更好的弹性,这种机制意味着DVM可以取底层资源的合集来提供上层应⽤使
⽤,差别只是在程序安装或者启动的过程中,DVM可以提⽰程序需求资源,本地环境可能未能满⾜⽽导致⽆法运⾏。DVM的Zygote并不是提供⼀个运⾏时容器,它提供的只是⼀个⽤于共享的进程,所有的应⽤程序运⾏,都是独⽴的,OS级别的进程,直接受到OS层⾯的资源控制以及调度的影响,只是他们共享Zygote说预加载的类⽽已。这也就是我为什么说,DVM就像是给每个应⽤程序在底层加了个套⼦,⽽不是提供了⼀个真正的运⾏时的VM。也就是说,DVM在开发环境中说提供的VM平台,和运⾏时的环境是很有可能不⼀致的。开发环境中提供的VM平台,是⼀个各种运⾏时可能环境的合集。
从这点上来说,⼀般我们认为,JVM中的JAVA程序的崩溃,最多导致JVM的崩溃,⽽不会导致OS崩溃,但是apk的崩溃,可以直接导致OS 崩溃,android⼿机会因为应⽤程序死机,⼤家应该是很常见了。但是⼤家⼀般是不会看到java程序导致死机吧?因为运⾏时中间隔着⼀个JVM。(当然,其实还是有些⼩门道可以⽤java程序让OS崩溃,因为这个,我和某些JAVA⼤拿打赌赢过饭局,呵呵,不过这是
其他话题,不在这⾥展开了)
除此之外,在JVM的机制中,不同的程序,打包以后,他们都是在运⾏层级真正独⽴的程序(指程序应⽤他们相互之间的关系,⽽不是和JVM的关系),即便他们在包⾥使⽤了同样的类,运⾏时都是单独加载,单独运⾏的(及加载多遍)。
DVM这种预加载-共享的机制,使得不同应⽤之间,在运⾏时,是共享相同的类的,⼀般来说,在系统资源消耗⽅⾯,拥有更⾼的效率。
最后,补充⼀点,byte code并不意味着就是解释执⾏,也能是加载编译,安装编译,预编译等等。实际上,不同的byte code的程序,不同的技术,不同的具体语⾔,其真正执⾏的情况是挺复杂,难以⼀概⽽论的,好多都是混合技术的案例,从我对odex的技术来看,就是个典型案例。当然这是题外话,不多展开了
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论