Dalvik虚拟机详解(上)

来源:互联网 发布:淘宝开店视频教学视频 编辑:程序博客网 时间:2024/05/22 14:42

什么是Dalvik?

    Android平台是为那些处理能力、内存、和存储等处理能力受限的设备而生。
    Android应用程序在运行时必须支持多种不同类型的设备,并且基于安全、性能和可靠性考虑必须被沙箱隔离。如此看来,,虚拟机貌似是非常合适的选择。
    但是虚拟机在运行时是没法帮你保证种类繁多的设备的处理速度、和ARM的差异等等,那么Google是如何处理这些冲突的呢?总的来说,他们的方法就是通过给予运行的程序如下的限制来实现:
    每一个Android应用和它的Dalvik实例都运行在它自己的进程中, Dalvik的设计初衷就是在同一个设备上支持大量的Dalvik实例高效运行的。Dalvik虚拟机在其内部的执行器中执行的是dex格式的文件。Dalvik虚拟机是基于寄存器的,并且执行的是通过dxtool工具将class文件转换后的dex文件。Dalvik虚拟机的一些因此的功能是依赖于Linux内核的,比如线程和底层的内存管理。
    要想Android应用在它自己的虚拟机进程内执行,不仅仅要求大量的虚拟机高效的运行而且还要求新虚拟机能够快速创建(用户会不定时打开新的应用)。

  class文件格式和dex文件格式如下图所示:

 
     dex文件使用了共享格式清晰的常量池机制。一个常量池存储了所有的在class文件中使用到的字面意义上的常量值,这些值包括诸如字符串常量、变量、类、接口和方法名称等。与class文件直接存储这些值不同,他们总是通过他们在常量池的下标来引用的。在class文件中,每一个类都有它自己私有的、种类繁多的常量池。
    这些种类繁多的常量(字段、变量、class 、etc)是混合着用的。和dex文件形成鲜明的对比的是,dex文件包含很多类,他们都公用一个类型明确的常量池。在class文件中的常量的副本在dex文件中被消除了。
    也许你会问难道共享一个单一的不同的线程池还不够吗,为什么要去使用类型明确的常量池?实际上类型明确的常量池最大程度上消除了重复的值.这样做有利于节省大笔内存。
   但是有必要强调的是,内存共享不是免费的,垃圾回收策略必须考虑到共享的内存,因为GC是每个应用所独立特有的,尽管他们共享了一些内存,但是每一个app都是一个独立的进程、独立的Dalvik虚拟机、独立的堆内存。目前的策略就是在Dalvik的GC中做标记位,或者标记为表明一个特殊的对象是可达的,因此不应该被回收、从其它的堆中分离出来。如果标记为是对象的堆上存储,当GC遍历堆设置标记的期间所有共享的对象将会直接“unshared”,记住,共享内存只能是只读的,不能改写。从其它的堆内存分离标记位可避免此陷阱。

什么是Zygote?

    Zygote的设计使用了一定数量的核心库类和符合标准的堆结构由应用程序使用,这种堆结构是只读的不能被改写。换句话说就是:这些数据和类是被大多数应用使用的,但是绝不能被修改。这些特性是用于跨进程内存共享。
    Zygote是一个虚拟机进程,当系统开机时就启动的,当一个Zygote进程启动时,他会初始化一个Dalvik虚拟机,这个徐帮你记会与先加载和初始化核心库类。通常这些核心库类是并且只读的,因此这是很有益处的对于跨进程共享和预加载来说。
一旦Zygote被初始化,它就会等待一个来自运行时的进程发送来的Socket请求,此请求用于告知Zygote是否需要基于Zygote虚拟机实例去fork一个新的VM实例。
冷启动一个虚拟机会花费比较长的时间它会将每个应用分离到他们自己的虚拟机内。通过Zygote孵化新的虚拟机进程就大大减少了系统启动时间了。
    核心库类在应用虚拟机实例之间是共享使用的,但是只能读取却不能被修改。当这些类被写入的时候,共享的Zygote进程的内存被复制去fork应用虚拟机的子进程同时写进去。
    在传统的java 虚拟机中,每个虚拟机的实例都将有一个核心库类文件和与之相关的堆对象的复本实例,虚拟机实例之间的内存是不共享的。

虚拟机如何启动?

    当系统启动时Zygote进程就会启动,当Zygote启动完成后,就会去监听一个socket上接收的命令。其它的进程比如ActivityManagerServie需要创建一个新的进程时就会把命令写入到那个Socket。而这个命令是由Zygote进程准备并且调用fork()方法产生的,所以子进程就获得一个虚拟机去运行了。

    具体来说就是:

    当Android系统的内核被加载后,init.rc会被解析同时本地服务启动,也就是system/bin/app_process开始运行(frameworks/base/cmds/app_process/app_main.cpp) )。这个进程最终会调用AndroidRuntime.start()方法(frameworks/base/core/jni/AndroidRuntime.cpp)),传递给他一个com.android.internal.os.ZygoteInt和start-system-server参数。AndroidRuntime.start()会启动Java虚拟机,然后调用ZygoteInit.main()方法,并且传递一个start-system-server参数。
     然后ZygoteInit.main()首先会去注册一个Zygote Socket(这个Zygote Socket用于监听接收到的指令,并且按照要求产生新的进程)。下一步就是预加载很多类(也就是在frameworks/base/preloaded-classes中的列表)以及整个系统的资源比如drawable、xml等。然后就会调用startSystemServer()开始为SystemServer孵化一个新的虚拟机进程(顺便说一句,在SystemServer中的main()方法就是主入口了)。与其他进程相比孵化SystemServer比较特殊,。
     在SystemServer孵化以后,runSelectLoopMode()方法被调用。这是一个while(true)死循环,是用于基本的ZygoteConnect的建立,主要是用于Zygote Socket等待新的命令。当收到新的命令时,ZygoteConnection.runOnce()方法被调用(frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java)。ZygoteConnection.runOnce()调用Zygote.forkAndSpecialize()就是简单的调用本地方法去fork。可以看出来,SystemServer进程和普通的进程是有区别的,SystemServer是完全由本地类app_main.cpp和AndroidRuntime.cpp来启动的,而普通的进程可以说是由SystemServer.java配合本地方法完成的。

   

                                    dalvik启动过程(图1)                                                                                dalvik启动过程(图2)

什么是dalvik-cache?

     通常在Android平台设备上,都会有一个data/dalvik-cache目录,该目录存放的是优化过的dex文件。通常第一次安装app时,系统会启动一个优化进程optimization  process来对dex文件进行优化,此优化文件被称为odex文件,此文件就存放在data/dalvik-cache目录下,以后app每次启动就不用再开启优化进程去优化文件了,这也是为什么第一次启动app时会比较缓慢,而之后启动会很快的原因。

如何理解Dalvik是基于寄存器的?

  详见我的另一篇博客:http://blog.csdn.net/u012481172/article/details/50904574;  

 友情链接:http://opensourceforu.efytimes.com/2011/06/virtual-machines-for-abstraction-dalvik-vm/

Dalvik的线程状态

  • INITIALIZING(初始化):还没开始运行。
  • STARTING(开始): 还没运行,但是一直存在。
  • ZOMBIE(僵尸?):死亡,我们看不到这种状态。
  • RUNNING(a/k/a RUNNABLE)(运行中):线程正在运行,虚拟机必须暂停(挂载)所有的线程去转存堆栈信息,所以我们通常不可能见到正在进行堆栈转存以外的任何一个线程。所谓堆栈转存就是将程序线程的执行状态通过dump文件保存或打印。
  • WAIT(等待):线程调用了wait()方法,正在耐心等待被唤醒。
  • TIMED_WAIT-:线程调用了wait()方法,并且指定了超时时限,Thread.sleep()就是通过限时等待实现的。
  • MONITOR(监听):线程被一个监听锁阻塞了,并试图进入一个“synchronized”块。
  • NATIVE(本地):线程正在执行本地代码,虚拟机不会暂停(挂载)本地线程,除非他们是用JNI调用的。
  • VMWAIT(虚拟机等待):线程被阻塞并获取一个虚拟机资源,比如内部的互斥,或者正在等待其它事情要做,比如编译器和GC线程。
  • SUSPENDED(暂停):线程可执行,但是已经被暂停了,正如前面提到的,堆栈转存在专业他们的堆栈时会暂停所有的线程,所以我们的繁忙的线程通常会以这种形式出现。

1 0
原创粉丝点击