Inside Dalvik VM资料汇总.docx

上传人:b****5 文档编号:7386179 上传时间:2023-01-23 格式:DOCX 页数:32 大小:638.55KB
下载 相关 举报
Inside Dalvik VM资料汇总.docx_第1页
第1页 / 共32页
Inside Dalvik VM资料汇总.docx_第2页
第2页 / 共32页
Inside Dalvik VM资料汇总.docx_第3页
第3页 / 共32页
Inside Dalvik VM资料汇总.docx_第4页
第4页 / 共32页
Inside Dalvik VM资料汇总.docx_第5页
第5页 / 共32页
点击查看更多>>
下载资源
资源描述

Inside Dalvik VM资料汇总.docx

《Inside Dalvik VM资料汇总.docx》由会员分享,可在线阅读,更多相关《Inside Dalvik VM资料汇总.docx(32页珍藏版)》请在冰豆网上搜索。

Inside Dalvik VM资料汇总.docx

InsideDalvikVM资料汇总

InsideDalvikVM

Contents

 [hide]

∙1 熊出没

∙2 源代码树

∙3 基于栈还是基于寄存器

∙4 Dalvik的运行时数据区

∙5 内存分配与垃圾回收

∙6 Dalvik解释器

∙7 JIT编译器

∙8 线程模型

∙9 参考资料

[edit]熊出没

据说Dalvik是冰岛Iceland的一个地名,当初Android团队在开发虚拟机时,不知道该用什么名称才好,最后DanBornstein说,就用Dalvik好了,Dalvik是一个小渔村,其祖辈曾在这个地方生活过,于是就有了今天被频繁提及的DalvikVM。

DanBornstein何许人也?

这个家伙是Dalvik开发团队的techlead,他设计了新的bytecode可执行格式,就是.dex,把.class文件转换成.dex的dx工具也是他写的,除此以外还做了很多事情,怪不得2008GoogleI/O大会上会由Dan来给大家讲解Dalvik,看来Dan的贡献度着实不小。

好,我们闲话少聊,在本节中,我们从系统启动的角度切入,找寻Dalvik出没的地方。

故事从init.rc和zygote开始讲起。

当linux启动时,会在init进程中解析init.rc脚本,并且根据脚本中的配置,创建一系列的进程,其中就包括zygote服务。

zygote的意思是“受精卵,结合子”,在android中,指的是,一旦有请求,Zygote进程会像老母鸡下蛋一样,fork出一个子进程。

servicezygote/system/bin/app_process-Xzygote/system/bin--zygote--start-system-server

参数暂且不管,我们所知道的是,一个叫zygote的linux进程被创建了,那么它的main()执行入口在哪?

我们注意到,zygote进程的执行代码在/system/bin/app_process文件中,这是一个linux可执行文件,其源代码在frameworks/base/cmds/app_process目录下。

我们在app_main.cpp中找到了zygote的main()函数。

我们摘取关键代码分析,

intmain(intargc,constchar*constargv[])

{

//...

if(0==strcmp("--zygote",arg)){

boolstartSystemServer=(i

strcmp(argv[i],"--start-system-server")==0 :

false;

setArgv0(argv0,"zygote");

set_process_name("zygote");

runtime.start("com.android.internal.os.ZygoteInit",

startSystemServer);

}

//...

}

这里我们发现,--zygote和--start-system-server和init.rc中zygote相关的参数吻合,而最关键的代码是runtime.start,其原型是AndroidRuntime:

:

start(xxx,xxx)(注意,是带参数的start版本,创建zygote服务),具体大家可以查看frameworks/base/core/jni/AndroidRuntime.cpp这个文件。

我们来简单整理一下调用过程,runtime.start->startVm->JNI_CreateJavaVM。

在start(xxx,xxx)函数中,做了以下三件事情,创建Dalvik虚拟机、注册native函数和调用ZygoteInit.java中的main()函数。

创建Dalvik虚拟机

/*

*InitializetheVM.

*

*TheJavaVM*isessentiallyper-process,andtheJNIEnv*isper-thread.

*Ifthiscallsucceeds,theVMisready,andwecanstartissuing

*JNIcalls.

*/

if(JNI_CreateJavaVM(&mJavaVM,&env,&initArgs)<0){

LOGE("JNI_CreateJavaVMfailed\n");

gotobail;

}

严格来讲,Dalvik虚拟机并不是Java虚拟机,一个虚拟机只有通过JCK(JavaComplianceKit)的测试并获得授权后才能称为JVM。

DalvikVM非但无法直接运行.class文件(只能执行dx工具从.class文件转换过的.dex文件),更没有获得授权。

不过话说回来,Android之所有开发Dalvik,除了性能上的考虑外,很大一部分原因就是要避开Sun的授权。

这里我们只需要了解,JNI_CreateJavaVM()创建了Dalvik虚拟机的一个实例。

虚拟机就像薄薄的一层纸,粘附在linux进程上,任何Java代码要想跑起来,都必须经过这层纸。

注册native函数

/*

*Registerandroidfunctions.

*/

if(startReg(env)<0){

LOGE("Unabletoregisterallandroidnatives\n");

gotobail;

}

所有的native函数在这里注册。

在AndroidRuntime.cpp中有一个函数指针表gRegJNI[],里面存放了各个模块的nativefunctions注册函数,每个注册函数会在startReg()中被逐一运行,因而每个模块的nativefunctions也被注册到虚拟机中。

当这一步执行完后,也就意味着,系统已经准备好执行Java代码了。

这正是接下来我们要讲述的。

调用ZygoteInit.java中的main()函数

//slashClassName对应ZygoteInit类

startClass=env->FindClass(slashClassName);

//找到ZygoteInit类的main()函数

startMeth=env->GetStaticMethodID(startClass,"main","([Ljava/lang/String;)V");

//开始执行Java代码

env->CallStaticVoidMethod(startClass,startMeth,strArray);

从CallStaticVoidMethod()开始,Java代码就开始在Dalvik虚拟机的Java解释器(Interpreter)上运行起来了。

事实上,Dalvik虚拟机用C语言构建,就好比linux上的一个应用程序,它的Java解释器(也是一些C代码)维持一个while循环,不停的执行Java代码中的每一条bytecode指令。

执行效率直接依赖于Interpreter的效率,所以,在dalvik的实现中,有两套版本,一套是C版本,另一套是ASM版本,其中ASM版本又对应着不同的平台,如ARM/MIPS/X86。

MIPS的版本应该是MIPS公司在Google发布Android版本后添加进去的,因为从Android官方网站上取下的代码中并不包含MIPS的ASM版本,可见Android对MIPS的支持远远不如ARM。

main()函数都干了些什么

前面解释到ZygoteInit类的main()函数开始被执行了,下面我们看看main()都做了哪些事情。

registerZygoteSocket();

if(argv[1].equals("true")){

startSystemServer();

}

runSelectLoopMode();

registerZygoteSocket()为zygote进程注册socket机制,我们知道,zygote进程都是通过socket机制接受子进程创建的请求的。

startSystemServer()是一个相当重要的步骤,zygote进程生下了它的第一个蛋,systemserver。

这一子进程为android系统创建了大部分的系统服务,包括在native服务和Android服务。

Native服务包括:

SurfaceFlinger/AudioFlinge/mediaplaybackservice/cameraservice

Android服务就更多了,

EntropyService/PowerManager/ActivityManager/TelephonyRegistry/PackageManager/AccountManagerContentManager/SystemContentProviders/BatteryService/HardwareService/AlarmManager/InitWatchdogSensorService/WindowManager/BluetoothService/Statusbar/ClipboardService/InputMethodServiceNetStatService/ConnectivityService/AccessibilityManager/NotificationManager/MountService/DeviceStorageMonitorLocationManager/SearchService/CheckinService/WallpaperService/AudioService/HeadsetObserverBackupService/AppWidgetService

现在来看Android的启动序列图,相信你会有更感性和深刻的认识,

接下来执行的runSelectLoopMode()就是一只真正的老母鸡了,正是在这里,一个又一个子进程被复制出来。

在这个函数里面,有一个while(true)循环,zygote会一直等待请求,一旦发现socket发出请求,zygote马上执行ZygoteConnnection.java类的runOnce()函数,首先创建出子进程,然后是通过pid的值来区分对待,于是父子在这里开始分道扬镳,zygote仍旧尽职的等待下一个请求的链接,子进程则做它自己事情去了,这也是子进程被创建出来的意义所在。

while(true)

{

//等待socket请求

acceptCommandPeer();

ZygoteConnection:

:

runOnce();

}

|

|

|

|

________________________|

|

|

|

\/

pid=Zygote.forkAndSpecialize();//通过jni呼叫linux的系统调用fork()

if(pid==0){

handleChildProc();

returntrue;

}else{

returnhandleParentProc();

}

zygote本身是一个虚拟机进程,同时也是一个应用进程的孵化器,每当系统要求执行一个Android应用程序时(当需要运行manifest文件中的中的类时,就会通过socket向zygote发送启动命令),zygote就会fork出一个子进程。

子进程同样是一个虚拟机进程,与zygote父进程共享代码空间,但是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝(包括和zygote一样的Dalvik虚拟机运行环境,程序计数器-pc也相同)。

只有一点不同,如果fork成功,子进程中fork的返回值是0,父进程中fork的返回值是子进程的进程号,因为也就有了上述代码的分叉执行。

子进程继续加载java程序的类和方法,并通过JavaInterpreter最终来解释并运行java程序。

这样做的好处显而易见:

zygote进程是在系统启动时产生的,它会完成虚拟机的初始化,库的加载,预置类库的加载和初始化等等操作,而在系统需要一个新的虚拟机实例时,Zygote通过复制自身,最快速的提供给系统。

另外,对于一些只读的系统库,所有虚拟机实例都和Zygote共享一块内存区域,大大节省了内存开销。

小结:

本节用一定的篇幅介绍了Android系统启动时Dalvik初始化的时机,以及zygote和虚拟机之间的那些事儿。

当提到Dalvik虚拟机时,我们不再感到茫然陌生,因为我们的脑海中已经有了一个虚拟机工作的模型,尽管模糊,却可触摸。

接下来我们将用一系列的文章深入Dalvik虚拟机,钻研其原理,关注其实现,展望其未来,敬请期待。

如无特别提及,文中所指均为Android2.2(froyo)的Dalvik实现。

[edit]源代码树

 

dalvik/

|

|----------dalvikvm/--Dalvikvm工具的源代码,针对某个类单独运行虚拟机

|

|----------dexdump/--dexdump工具的源代码,类似于objdump

|

|----------dexlist/--dexlist工具的源代码,可列出dex文件中所有类的method

|

|----------dexopt/--Dalvik优化工具,可以对加载的dex文件进行验证与优化,生成一个classes.dex文件,

|如:

/data/dalvik-cache/system@framework@ext.jar@classes.dex

|

|----------docs/--dalvik的一些设计文档

|

|----------dvz/

|

|----------dx/--dx负责将Java的.class文件转换成Dalvik可执行的.dex文件,这里是源代码

|

|----------hit/

|

|----------libcore/--核心库的源代码主要本目录和dalvik/vm/native中,很多都是来自ApacheHarmony项目,也有一些是来自OpenSSL、zlib和ICU等项目

|

|----------libdex/--.dexfile的加载和解析,值得一提的是,InstrUtils.c/h提供了对Dalvik虚拟机指令的解析,比如解析操作码和操作数

|

|----------libnativehelper/

|

|----------tests/

|

|----------tools/

|

|----------vm/--Dalvik的源代码,研究Dalvik主要关注这个目录

||

||----------analysis/--class/method的分析/验证/优化

||

||----------arch/--JNIcallbridge,从Java端调用native函数时,需要根据不同平台对C函数的参数顺序和返回结果进行调整,比如,

||x86的参数是从右往左推入栈中,结果通过eax(必要的话,加上edx)返回,

||arm的参数从左到右,依次放入r0,r1,r2,r3寄存器,大于4个的参数从右往左压入栈中,结果通过r0(必要的话,加上r1)。

||若使用的平台不在JNIcallBrige支持范围时,系统会使用libffi库,该库性能稍差。

||

||----------hprof/--heapprofile调试工具的相关实现

||

||----------interp/--Interpreter的主架构实现,调用mterp目录中的bytecode,完成虚拟机指令的执行

||Stack.c定义了Dalvik栈的初始化,帧入栈,帧出栈和栈的dump等操作

||Jit.c/h中定义了供外界调用的接口

||

||----------jdwp/--JavaDebugWireProtocol的相关实现

||

||----------mterp/--Interpreter的bytecode功能实现,包括C和ASM版本

||

||----------native/--核心库的源代码主要本目录和dalvik/libcore中,很多都是来自ApacheHarmony项目,也有一些是来自OpenSSL、zlib和ICU等项目

||

||----------oo/--处理一些面向对象的特性,如解析加载Java类,创建虚拟函数表vtable,接口表iftable,

||解析加载directMethods,virtualMethods,sfields和ifields,

||类hash表gDvm.loadedClasses的创建/查找/销毁/扫描(为GC回收做标记而扫描),ClassObject的dump等

||

||----------reflect/--实现Java的反射机制,该特性允许运行中的Java程序对自身检查,并直接操作程序的内部属性

||

||----------test/

||

||----------CheckJni.c--对Object/Class/String有效性的验证,更重要的是,提供了gCheckNativeInterface[]和gCheckInvokeInterface[]

||这两个表被挂载到JNIEnvExt和JavaVMExt数据结构中的funcTable指针

||

||----------Hash.c--对hash表的操作,包括:

创建/清除/释放/扩展/移除/查询/遍历等

||

||----------Init.c/Jni.c--Dalvik的初始化代码皆在此,包括JNI_CreateJavaVM

||

||----------LinearAlloc.c--从ashmem划出一块“dalvik-LinearAlloc”内存区域,大小为4M,专门用于存放那些只写一次的数据,

||比如class的vtables,fields,methods,interfaces等

||

||----------Native.c--在解释器执行Java代码时,如碰到nativemethod,需怎样定位和执行,本文件给出了答案

||

||----------ReferenceTable.c--引用表的初始化/销毁/dump,对象引用的入表/移除,查询和对比

||

||----------Thread.c--线程的操作,包括:

线程的创建/销毁,挂起/继续,dump/scan(为GC标记而进行的扫描)等

||

||

||----------alloc/--Dalvik内存分配和垃圾回收的实现源代码

|||

|||----------Heap.c--整个GcHeap的初始化/销毁,内存的分配,GC的触发

|||

|||----------HeapSource.c--一个HeapSource最多可拥有3个heap,所有的对象都是从这几个heap中分配,调用dlmalloc模块

|||

|||----------HeapWorker.c--HeapWorker线程执行某些对象的finalize(),在GC回收时被唤醒

|||

|||----------HeapTable.c--对引用表的操作实现,比如初始化/释放,加入/移除对象和对表中对象的标记

|||

|||----------HeapBitmap.c/h--堆位图(HeapBitmap)的分配/释放/遍历/查找/置位/清位,堆位图空间中的一个bit对应堆上的8个字节

|||堆位图空间在androidsharedmemory中分配,通过mmap()映射到本进程

|||

|||----------MarkSweep.c--对象的标记-清除算法实现

||

||

||----------compiler/--JIT的相关实现

|||

|||----------Compiler.c--compiler线程的代码实现,以及编译任务队列的管理

|||

|||----------Frontend.c--method/trace单元的编译函数实现

|||

|||----------Dataflow.c--编译框架的实现

|||IntermediateRep.c

|||Loop.c

|||Utility.c

|||

|||----------codegen/--不同架构的编译实现,如arm,x86,mips

|||

|||----------template/--

|||

[edit]基于栈还是基于寄存器

首先一点,指令由操作码(opcode)和操作数(operand)组成。

当我们谈及虚拟机是栈架构还是寄存器架构时,是针对虚拟机指令中操作数的访问方式而言的。

我们看一行简单的代码,

a=b+c

在基于寄存器的指令集架构(DalvikVM)中,该行代码将被翻译成下面的指令,大家看到之后是否感觉很熟悉?

因为平常所见的处理器如ARM/MIPS/X86等的指令集都基于寄存器(当然,虚拟机指令中的虚拟寄存器和处理器中的物理寄存器不同,虚拟寄存器可能被存放在内存中,也有可能被映射成处理器的物理寄存器,视实现而定)。

在这类指令集中,源操作数和目的操作数需要在指令码中显式指定,由于操作数个数不定,导致指令码的长度长短不同,因而在执行时需要更多的内存空间。

IADDa,b,c

在基于栈的指令集架构(JVM)中,操作数从Java虚拟栈中的操作数栈(operandstack)取得,伴随着指令的执行,不断的有操作数出入栈。

在下面的Java字节码中,前两条字节码指令从Java虚拟栈的局部变量区读取b和c的值,并推入操作数栈,IADD从操作数栈弹出2个值,相加,然后把结果推入栈顶,ISTORE从操作数栈弹出一个值,保存到局部变量区。

由于字节码无需在指令中指定操作数,因而字节码的密度可以非常高,但是,完成同样的工作,基于栈架构的系统比基于寄存器架构的系统需要更多条的指令。

ILOADc

ILOADb

IADD

ISTOREa

网上一些文章在讨论Dalvik时,大都简单提及Dalvik执行速度比JVM快,但移植性

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工作范文 > 制度规范

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1