Dalvik虚拟机简要介绍转载
来源:互联网 发布:淘宝做的最好的店铺 编辑:程序博客网 时间:2024/06/07 02:28
转载来着老罗的http://blog.csdn.net/luoshengyang/博客,
Android应用程序是运行在Dalvik虚拟机里面的,并且每一个应用程序对应有一个单独的Dalvik虚拟机实例。除了指令集和类文件格式不同,Dalvik虚拟机与Java虚拟机共享有差不多的特性,例如,它们都是解释执行,并且支持即时编译(JIT)、垃圾收集(GC)、Java本地方法调用(JNI)和Java远程调试协议(JDWP)等。本文对Dalvik虚拟机进行简要介绍,以及制定学习计划。
Dalvik虚拟机是由Dan Bornstein开发的,名字来源于他的祖先曾经居住过的位于冰岛的同名小渔村。Dalvik虚拟机起源于Apache Harmony项目,后者是由Apache软件基金会主导的,目标是实现一个独立的、兼容JDK 5的虚拟机,并根据Apache License v2发布。由此可见,Dalvik虚拟机从诞生的那一天开始,就和Java有说不清理不断的关系。
Dalvik虚拟机与Java虚拟机的最显著区别是它们分别具有不同的类文件格式以及指令集。Dalvik虚拟机使用的是dex(Dalvik Executable)格式的类文件,而Java虚拟机使用的是class格式的类文件。一个dex文件可以包含若干个类,而一个class文件只包括一个类。由于一个dex文件可以包含若干个类,因此它就可以将各个类中重复的字符串和其它常数只保存一次,从而节省了空间,这样就适合在内存和处理器速度有限的手机系统中使用。一般来说,包含有相同类的未压缩dex文件稍小于一个已经压缩的jar文件。
Dalvik虚拟机使用的指令是基于寄存器的,而Java虚拟机使用的指令集是基于堆栈的。基于堆栈的指令很紧凑,例如,Java虚拟机使用的指令只占一个字节,因而称为字节码。基于寄存器的指令由于需要指定源地址和目标地址,因此需要占用更多的指令空间,例如,Dalvik虚拟机的某些指令需要占用两个字节。基于堆栈和基于寄存器的指令集各有优劣,一般而言,执行同样的功能,前者需要更多的指令(主要是load和store指令),而后者需要更多的指令空间。需要更多指令意味着要多占用CPU时间,而需要更多指令空间意味着指令缓冲(i-cache)更易失效。
此外,还有一种观点认为,基于堆栈的指令更具可移植性,因为它不对目标机器的寄存器进行任何假设。然而,基于寄存器的指令由于对目标机器的寄存器进行了假设,因此,它更有利于进行AOT(ahead-of-time)优化。所谓AOT,就是在解释语言程序运行之前,就先将它编译成本地机器语言程序。AOT本质上是一种静态编译,它是是相对于JIT而言的,也就是说,前者是在程序运行前进行编译,而后者是在程序运行时进行编译。运行时编译意味着可以利用运行时信息来得到比较静态编译更优化的代码,同时也意味不能进行某些高级优化,因为优化过程太耗时了。另一方面,运行前编译由于不占用程序运行时间,因此,它就可以不计时间成本来优化代码。无论AOT,还是JIT,最终的目标都是将解释语言编译为本地机器语言,而本地机器语言都是基于寄存器来执行的,因此,在某种程度来讲,基于寄存器的指令更有利于进行AOT编译以及优化。
事实上,基于寄存器和基于堆栈的指令集之争,就如精简指令集(RISC)和复杂指令集(CISC)之争,谁优谁劣,至今是没有定论的。例如,上面提到完成相同的功能,基于堆栈的Java虚拟机需要更多的指令,因此就会比基于寄存器的Dalvik虚拟机慢,然而,在2010年,Oracle在一个ARM设备上使用一个non-graphical Java benchmarks来对比Java SE Embedded和Android 2.2的性能,发现后者比前者慢了2~3倍。上述性能比较结论以及数据可以参考以下两篇文章:
1. Virtual Machine Showdown: Stack Versus Registers
2. Java SE Embedded Performance Versus Android 2.2
基于寄存器的Dalvik虚拟机和基于堆栈的Java虚拟机的更多比较和分析,还可以参考以下文章:
1. http://en.wikipedia.org/wiki/Dalvik_(software)
2. http://www.infoq.com/news/2007/11/dalvik
3. http://www.zhihu.com/question/20207106
不管结论如何,Dalvik虚拟机都在尽最大的努力来优化自身,这些措施包括:
1.将多个类文件收集到同一个dex文件中,以便节省空间;
2.使用只读的内存映射方式加载dex文件,以便可以多进程共享dex文件,节省程序加载时间;
3.提前调整好字节序(byte order)和字对齐(word alignment)方式,使得它们更适合于本地机器,以便提高指令执行速度;
4.尽量提前进行字节码验证(bytecode verification),提高程序的加载速度;
5.需要重写字节码的优化要提前进行。
这些优化措施的更具体描述可以参考Dalvik Optimization and Verification With dexopt一文。
分析完Dalvik虚拟机和Java虚拟机的区别之后,接下来我们再简要分析一下Dalvik虚拟机的其它特性,包括内存管理、垃圾收集、JIT、JNI以及进程和线程管理。
一. 内存管理
Dalvik虚拟机的内存大体上可以分为Java Object Heap、Bitmap Memory和Native Heap三种。
Java Object Heap是用来分配Java对象的,也就是我们在代码new出来的对象都是位于Java Object Heap上的。Dalvik虚拟机在启动的时候,可以通过-Xms和-Xmx选项来指定Java Object Heap的最小值和最大值。为了避免Dalvik虚拟机在运行的过程中对Java Object Heap的大小进行调整而影响性能,我们可以通过-Xms和-Xmx选项来将它的最小值和最大值设置为相等。
Java Object Heap的最小和最大默认值为2M和16M,但是手机在出厂时,厂商会根据手机的配置情况来对其进行调整,例如,G1、Droid、Nexus One和Xoom的Java Object Heap的最大值分别为16M、24M、32M和48M。我们可以通过ActivityManager类的成员函数getMemoryClass来获得Dalvik虚拟机的Java Object Heap的最大值。
ActivityManager类的成员函数getMemoryClass的实现如下所示:
[java]view plaincopyprint?
1. public class ActivityManager {
2. ......
3.
4. /**
5. * Return the approximate per-application memory class of the current
6. * device. This gives you an idea of how hard a memory limit you should
7. * impose on your application to let the overall system work best. The
8. * returned value is in megabytes; the baseline Android memory class is
9. * 16 (which happens to be the Java heap limit of those devices); some
10. * device with more memory may return 24 or even higher numbers.
11. */
12. public int getMemoryClass() {
13. return staticGetMemoryClass();
14. }
15.
16. /** @hide */
17. static public int staticGetMemoryClass() {
18. // Really brain dead right now -- just take this from the configured
19. // vm heap size, and assume it is in megabytes and thus ends with "m".
20. String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m");
21. return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1));
22. }
23.
24. ......
25. }
public class ActivityManager {
......
/**
* Return the approximate per-application memory class of the current
* device. This gives you an idea of how hard a memory limit you should
* impose on your application to let the overall system work best. The
* returned value is in megabytes; the baseline Android memory class is
* 16 (which happens to be the Java heap limit of those devices); some
* device with more memory may return 24 or even higher numbers.
*/
public int getMemoryClass() {
return staticGetMemoryClass();
}
/** @hide */
static public int staticGetMemoryClass() {
// Really brain dead right now -- just take this from the configured
// vm heap size, and assume it is in megabytes and thus ends with "m".
String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m");
return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1));
}
......
}
这个函数定义在文件frameworks/base/core/java/android/app/ActivityManager.java中。
Dalvik虚拟机在启动的时候,就是通过读取系统属性dalvik.vm.heapsize的值来获得Java Object Heap的最大值的,而ActivityManager类的成员函数getMemoryClass最终也通过读取这个系统属性的值来获得Java Object Heap的最大值。
这个Java Object Heap的最大值也就是我们平时所说的Android应用程序进程能够使用的最大内存。这里必须要注意的是,Android应用程序进程能够使用的最大内存指的是能够用来分配Java Object的堆。
Bitmap Memory也称为External Memory,它是用来处理图像的。在HoneyComb之前,Bitmap Memory是在Native Heap中分配的,但是这部分内存同样计入Java Object Heap中,也就是说,Bitmap占用的内存和Java Object占用的内存加起来不能超过Java Object Heap的最大值。这就是为什么我们在调用BitmapFactory相关的接口来处理大图像时,会抛出一个OutOfMemoryError异常的原因:
[html]view plaincopyprint?
1. java.lang.OutOfMemoryError: bitmap size exceeds VM budget
java.lang.OutOfMemoryError: bitmap size exceeds VM budget
在HoneyComb以及更高的版本中,Bitmap Memory就直接是在Java Object Heap中分配了,这样就可以直接接受GC的管理。
Native Heap就是在Native Code中使用malloc等分配出来的内存,这部分内存是不受Java Object Heap的大小限制的,也就是它可以自由使用,当然它是会受到系统的限制。但是有一点需要注意的是,不要因为Native Heap可以自由使用就滥用,因为滥用Native Heap会导致系统可用内存急剧减少,从而引发系统采取激进的措施来Kill掉某些进程,用来补充可用内存,这样会影响系统体验。
此外,在HoneyComb以及更高的版本中,我们可以在AndroidManifest.xml的application标签中增加一个值等于“true”的android:largeHeap属性来通知Dalvik虚拟机应用程序需要使用较大的Java Object Heap。事实上,在内存受限的手机上,即使我们将一个应用程序的android:largeHeap属性设置为“true”,也是不能增加它可用的Java Object Heap的大小的,而即便是可以通过这个属性来增大Java Object Heap的大小,一般情况也不应该使用该属性。为了提高系统的整体体验,我们需要做的是致力于降低应用程序的内存需求,而不是增加增加应用程序的Java Object Heap的大小,毕竟系统总共可用的内存是固定的,一个应用程序用得多了,就意味意其它应用程序用得少了。
二. 垃圾收集(GC)
Dalvik虚拟机可以自动回收那些不再使用了的Java Object,也就是那些不再被引用了的Java Object。垃圾自动收集机制将开发者从内存问题中解放出来,极大地提高了开发效率,以及提高了程序的可维护性。
我们知道,在C或者C++中,开发者需要手动地管理在堆中分配的内存,但是这往往导致很多问题。例如,内存分配之后忘记释放,造成内存泄漏。又如,非法访问那些已经释放了的内存,引发程序崩溃。如果没有一个好的C或者C++应用程序开发框架,一般的开发者根本无法驾驭内存问题,因为程序大了之后,很容易造成失控。最要命的是,内存被破坏的时候,并不一定就是程序崩溃的时候,它就是一颗不定时炸弹,说不准什么时候会被引爆,因此,查找原因是非常困难的。
从这里我们也可以推断出,Android为什么会选择Java而不是C/C++来作来应用程序开发语言,就是为了能够让开发远离内存问题,而将精力集中在业务上,开发出更多更好的APP来,从而迎头赶超iOS。当然,Android系统内存也存在大量的C/C++代码,这只要考虑性能问题,毕竟C/C++程序的运行性能整体上还是优于运行在虚拟机之上的Java程序的。不过,为了避免出现内存问题,在Android系统内部的C++代码码,大量地使用了智能指针来自动管理对象的生命周期。选择Java来作为Android应用程序的开发语言,可以说是技术与商业之间一个折衷,事实证明,这种折衷是成功的。
回到正题,在GingerBread之前,Dalvik虚拟使用的垃圾收集机制有以下特点:
1. Stop-the-word,也就是垃圾收集线程在执行的时候,其它的线程都停止;
2. Full heap collection,也就是一次收集完全部的垃圾;
3.一次垃圾收集造成的程序中止时间通常都大于100ms。
在GingerBread以及更高的版本中,Dalvik虚拟使用的垃圾收集机制得到了改进,如下所示:
1. Cocurrent,也就是大多数情况下,垃圾收集线程与其它线程是并发执行的;
2. Partial collection,也就是一次可能只收集一部分垃圾;
3. 一次垃圾收集造成的程序中止时间通常都小于5ms。
Dalvik虚拟机执行完成一次垃圾收集之后,我们通常可以看到类似以下的日志输出:
[html]view plaincopyprint?
1. D/dalvikvm(9050): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms
D/dalvikvm(9050): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms
在这一行日志中,GC_CONCURRENT表示GC原因,2049K表示总共回收的内存,3571K/9991K表示Java Object Heap统计,即在9991K的Java Object Heap中,有3571K是正在使用的,4703K/5261K表示External Memory统计,即在5261K的External Memory中,有4703K是正在使用的,2ms+2ms表示垃圾收集造成的程序中止时间。
三. 即时编译(JIT)
前面提到,JIT是相对AOT而言的,即JIT是在程序运行的过程中进行编译的,而AOT是在程序运行前进行编译的。在程序运行的过程中进行编译既有好处,也有坏处。好处在于可以利用程序的运行时信息来对编译出来的代码进行优化,而坏处在于占用程序的运行时间,也就是说不能花太多时间在代码编译和优化之上。
为了解决时间问题,JIT可能只会选择那些热点代码进行编译或者优化。根据2-8原则,一个程序80%的时间可能都是在重复执行20%的代码。因此,JIT就可以选择这20%经常执行的代码来进行编译和优化。
为了充分地利用好运行时信息来优化代码,JIT采用一种激进的方法。JIT在编译代码的时候,会对程序的运行情况进行假设,并且按照这种假设来对代码进行优化。随着程序的代码,如果前面的假设一直保持成立,那么JIT就什么也不用做,因此就可以提高程序的运行性能。一旦前面的假设不再成立了,那么JIT就需要对前面编译优化的代码进行调整,以便适应新的情况。这种调整成本可能是很昂贵的,但是只要假设不成立的情况很少或者几乎不会发生,那么获得的好处还是大于坏处的。由于JIT在编译和优化代码的时候,对程序的运行情况进行了假设,因此,它所采取的激进优化措施又称为赌博,即Gambling。
我们以一个例子来说明这种Gambling。我们知道,Java的同步原语涉及到Lock和Unlock操作。Lock和Unlock操作是非常耗时的,而且它们只有在多线程环境中才真的需要。但是一些同步函数或者同步代码,有程序运行的时候,有可能始终都是被单线程执行,也就是说,这些同步函数或者同步代码不会被多线程同时执行。这时候JIT就可以采取一种Lazy Unlocking机制。
当一个线程T1进入到一个同步代码C时,它还是按照正常的流程来获取一个轻量级锁L1,并且线程T1的ID会记录在轻量锁L1上。当经程T1离开同步函数或者同步代码时,它并不会释放前面获得的轻量级锁L1。当线程T1再次进入同步代码C时,它就会发现轻量级锁L的所有者正是自己,因此,它就可以直接执行同步代码C。这时候如果另外一个线程T2也要进入同步代码C,它就会发现轻量级锁L已经被线程T1获取。在这种情况下,JIT就需要检查线程T1的调用堆栈,看看它是否还在执行同步代码C。如果是的话,那么就需要将轻量级锁L1转换成一个重量级锁L2,并且将重量级锁L2的状态设置为锁定,然后再让线程T2在重量级锁L2上睡眠。等线程T1执行完成同步代码C之后,它就会按照正常的流程来释放重量级锁L2,从而唤醒线程T2来执行同步代码C。另一方面,如果线程T2在进入同步代码C的时候,JIT通过检查线程T1的调用堆栈,发现它已经离开同步代码C了,那么它就直接将轻量级锁L1的所有者记录为线程T2,并且让线程T2执行同步代码C。
通过上述的Lazy Unlocking机制,我们就可以充分地利用程序的运行时信息来提高程序的执行性能,这种优化对于静态编译的语言来说,是无法做到的。从这个角度来看,我们就可以说,静态编译语言(如C++)并不一定比在虚拟机上执行的语言(如Java)快,这是因为后者可以有一种强大的武器叫做JIT。
Dalvik虚拟机从Android 2.2版本开始,才支持JIT,而且是可选的。在编译Dalvik虚拟机的时候,可以通过WITH_JIT宏来将JIT也编译进去,而在启动Dalvik虚拟机的时候,可以通过-Xint:jit选项来开启JIT功能。
关于虚拟机JIT的实现原理的简要介绍,可以进一步参考这篇文章:http://blog.reverberate.org/2012/12/hello-jit-world-joy-of-simple-jits.html。
四. Java本地调用(JNI)
无论如何,虚拟机最终都是运行在目标机器之上的,也就是说,它需要将自己的指令翻译成目标机器指令来执行,并且有些功能,需要通过调用目标机器运行的操作系统接口来完成。这样就需要有一个机制,使得函数调用可以从Java层穿越到Native层,也就是C/C++层。这种机制就称为Java本地调用,即JNI。当然,我们在执行Native代码的时候,有时候也是需要调用到Java函数的,这同样是可以通过JNI机制来实现。也就是说,JNI机制既支持在Java函数中调用C/C++函数,也支持在C/C++函数中调用Java函数。
事实上,Dalvik虚拟机提供的Java运行时库,大部分都是通过调用目标机器操作系统接口来实现的,也就是通过调用Linux系统接口来实现的。例如,当我们调用android.os.Process类的成员函数start来创建一个进程的时候,最终会调用到Linux系统提供的fork系统调用来创建一个进程。
同时,为了方便开发者使用C/C++语言来开发应用程序,Android官方提供了NDK。通过NDK,我们就可以使用JNI机制来在Java函数中调用到C/C++函数。不过Android官方是不提倡使用NDK来开发应用程序的,这从它对NDK的支持远远不如SDK的支持就可以看得出来。
五. 进程和线程管理
一般来说,虚拟机的进程和线程都是与目标机器本地操作系统的进程和线程一一对应的,这样做的好处是可以使本地操作系统来调度进程和线程。进程和线程调度是操作系统的核心模块,它的实现是非常复杂的,特别是考虑到多核的情况,因此,就完全没有必要在虚拟机中提供一个进程和线程库。
Dalvik虚拟机运行在Linux操作系统之上。我们知道,Linux操作系统并没有纯粹的线程概念,只要两个进程共享同一个地址空间,那么就可以认为它们同一个进程的两个线程。Linux操作系统提供了两个fork和clone两个调用,其中,前者就是用来创建进程的,而后者就是用来创建线程的。关于Linux操作系统的进程和线程的实现,可以参考在前面Android学习启动篇一文中提到的经典Linux内核书籍。
关于Android应用程序进程,它有两个很大的特点,下面我们就简要介绍一下。
第一个特点是每一个Android应用程序进程都有一个Dalvik虚拟机实例。这样做的好处是Android应用程序进程之间不会相互影响,也就是说,一个Android应用程序进程的意外中止,不会影响到其它的Android应用程序进程的正常运行。
第二个特点是每一个Android应用程序进程都是由一种称为Zygote的进程fork出来的。Zygote进程是由init进程启动起来的,也就是在系统启动的时候启动的。Zygote进程在启动的时候,会创建一个虚拟机实例,并且在这个虚拟机实例将所有的Java核心库都加载起来。每当Zygote进程需要创建一个Android应用程序进程的时候,它就通过复制自身来实现,也就是通过fork系统调用来实现。这些被fork出来的Android应用程序进程,一方面是复制了Zygote进程中的虚拟机实例,另一方面是与Zygote进程共享了同一套Java核心库。这样不仅Android应用程序进程的创建过程很快,而且由于所有的Android应用程序进程都共享同一套Java核心库而节省了内存空间。
关于Dalvik虚拟机的特性,我们就简要介绍到这里。事实上,Dalvik虚拟机和Java虚拟机的实现是类似的,例如,Dalvik虚拟机也支持JDWP(Java Debug Wire Protocol)协议,这样我们就可以使用DDMS来调试运行在Dalvik虚拟机中的进程。对Dalvik虚拟机的其它特性或者实现原理有兴趣的,建议都可以参考Java虚拟机的实现,这里提供三本参考书:
1. Java Virtual Machine Specification (Java SE 7)
2. Inside the Java Virtual Machine, Second Edition
3. Oracle JRockit: The Definitive Guide
另外,关于Dalvik虚拟机的指令集和dex文件格式的介绍,可以参考官方文档:http://source.android.com/tech/dalvik/index.html。如果对虚拟机的实现原理有兴趣的,还可以参考这个链接:http://www.weibo.com/1595248757/zvdusrg15。
在这里,我们学习Dalvik虚拟机的目标是打通Java层到C/C++层之间的函数调用,从而可以更好地理解Android应用程序是如何在Linux内核上面运行的。为了达到这个目的,在接下来的文章中,我们将关注以下四个情景:
1.Dalvik虚拟机的启动过程;
2. Dalvik虚拟机的运行过程;
3. JNI函数的注册过程;
4. Java进程和线程的创建过程。
掌握了这四个情景之后,再结合前面的所有文章,我们就可以从上到下地打通整个Android系统了,敬请关注!
在Android系统中,应用程序进程都是由Zygote进程孵化出来的,而Zygote进程是由Init进程启动的。Zygote进程在启动时会创建一个Dalvik虚拟机实例,每当它孵化一个新的应用程序进程时,都会将这个Dalvik虚拟机实例复制到新的应用程序进程里面去,从而使得每一个应用程序进程都有一个独立的Dalvik虚拟机实例。在本文中,我们就分析Dalvik虚拟机在Zygote进程中的启动过程。
Zygote进程在启动的过程中,除了会创建一个Dalvik虚拟机实例之外,还会将Java运行时库加载到进程中来,以及注册一些Android核心类的JNI方法来前面创建的Dalvik虚拟机实例中去。注意,一个应用程序进程被Zygote进程孵化出来的时候,不仅会获得Zygote进程中的Dalvik虚拟机实例拷贝,还会与Zygote一起共享Java运行时库,这完全得益于Linux内核的进程创建机制(fork)。这种Zygote孵化机制的优点是不仅可以快速地启动一个应用程序进程,还可以节省整体的内存消耗,缺点是会影响开机速度,毕竟Zygote是在开机过程中启动的。不过,总体来说,是利大于弊的,毕竟整个系统只有一个Zygote进程,而可能有无数个应用程序进程,而且我们不会经常去关闭手机,大多数情况下只是让它进入休眠状态。
从前面Android系统进程Zygote启动过程的源代码分析一文可以知道,Zygote进程在启动的过程中,会调用到AndroidRuntime类的成员函数start,接下来我们就这个函数开始分析Dalvik虚拟机启动相关的过程,如图1所示:
图1 Dalvik虚拟机的启动过程
这个过程可以分为8个步骤,接下来我们就详细分析每一个步骤。
Step 1. AndroidRuntime.start
[cpp]view plaincopyprint?
1. void AndroidRuntime::start(const char* className, const bool startSystemServer)
2. {
3. ......
4.
5. /* start the virtual machine */
6. if (startVm(&mJavaVM, &env) != 0)
7. goto bail;
8.
9. /*
10. * Register android functions.
11. */
12. if (startReg(env) < 0) {
13. LOGE("Unable to register all android natives\n");
14. goto bail;
15. }
16.
17. ......
18.
19. /*
20. * Start VM. This thread becomes the main thread of the VM, and will
21. * not return until the VM exits.
22. */
23. jclass startClass;
24. jmethodID startMeth;
25.
26. slashClassName = strdup(className);
27. for (cp = slashClassName; *cp != '\0'; cp++)
28. if (*cp == '.')
29. *cp = '/';
30.
31. startClass = env->FindClass(slashClassName);
32. if (startClass == NULL) {
33. LOGE("JavaVM unable to locate class '%s'\n", slashClassName);
34. /* keep going */
35. } else {
36. startMeth = env->GetStaticMethodID(startClass, "main",
37. "([Ljava/lang/String;)V");
38. if (startMeth == NULL) {
39. LOGE("JavaVM unable to find main() in '%s'\n", className);
40. /* keep going */
41. } else {
42. env->CallStaticVoidMethod(startClass, startMeth, strArray);
43. ......
44. }
45. }
46.
47. LOGD("Shutting down VM\n");
48. if (mJavaVM->DetachCurrentThread() != JNI_OK)
49. LOGW("Warning: unable to detach main thread\n");
50. if (mJavaVM->DestroyJavaVM() != 0)
51. LOGW("Warning: VM did not shut down cleanly\n");
52.
53. ......
54. }
void AndroidRuntime::start(const char* className, const bool startSystemServer)
{
......
/* start the virtual machine */
if (startVm(&mJavaVM, &env) != 0)
goto bail;
/*
* Register android functions.
*/
if (startReg(env) < 0) {
LOGE("Unable to register all android natives\n");
goto bail;
}
......
/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/
jclass startClass;
jmethodID startMeth;
slashClassName = strdup(className);
for (cp = slashClassName; *cp != '\0'; cp++)
if (*cp == '.')
*cp = '/';
startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
LOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
LOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);
......
}
}
LOGD("Shutting down VM\n");
if (mJavaVM->DetachCurrentThread() != JNI_OK)
LOGW("Warning: unable to detach main thread\n");
if (mJavaVM->DestroyJavaVM() != 0)
LOGW("Warning: VM did not shut down cleanly\n");
......
}
这个函数定义在文件frameworks/base/core/jni/AndroidRuntime.cpp中。
AndroidRuntime类的成员函数start主要做了以下四件事情:
1.调用成员函数startVm来创建一个Dalvik虚拟机实例,并且保存在成员变量mJavaVM中。
2.调用成员函数startReg来注册一些Android核心类的JNI方法。
3.调用参数className所描述的一个Java类的静态成员函数main,来作为Zygote进程的Java层入口。从前面Android系统进程Zygote启动过程的源代码分析一文可以知道,这个入口类就为com.android.internal.os.ZygoteInit。执行这一步的时候,Zygote进程中的Dalvik虚拟机实例就开始正式运作了。
4.从com.android.internal.os.ZygoteInit类的静态成员函数main返回来的时候,就说明Zygote进程准备要退出来了。在退出之前,会调用前面创建的Dalvik虚拟机实例的成员函数DetachCurrentThread和DestroyJavaVM。其中,前者用来将Zygote进程的主线程脱离前面创建的Dalvik虚拟机实例,后者是用来销毁前面创建的Dalvik虚拟机实例。
接下来,我们就主要关注Dalvik虚拟机实例的创建过程,以及Android核心类JNI方法的注册过程,即AndroidRuntime类的成员函数startVm和startReg的实现。
Step 2. AndroidRuntime.startVm
[cpp]view plaincopyprint?
1. int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
2. {
3. int result = -1;
4. JavaVMInitArgs initArgs;
5. JavaVMOption opt;
6. ......
7.
8. property_get("dalvik.vm.checkjni", propBuf, "");
9. if (strcmp(propBuf, "true") == 0) {
10. checkJni = true;
11. } else if (strcmp(propBuf, "false") != 0) {
12. /* property is neither true nor false; fall back on kernel parameter */
13. property_get("ro.kernel.android.checkjni", propBuf, "");
14. if (propBuf[0] == '1') {
15. checkJni = true;
16. }
17. }
18. ......
19.
20. property_get("dalvik.vm.execution-mode", propBuf, "");
21. if (strcmp(propBuf, "int:portable") == 0) {
22. executionMode = kEMIntPortable;
23. } else if (strcmp(propBuf, "int:fast") == 0) {
24. executionMode = kEMIntFast;
25. #if defined(WITH_JIT)
26. } else if (strcmp(propBuf, "int:jit") == 0) {
27. executionMode = kEMJitCompiler;
28. #endif
29. }
30.
31. property_get("dalvik.vm.stack-trace-file", stackTraceFileBuf, "");
32. ......
33.
34. strcpy(heapsizeOptsBuf, "-Xmx");
35. property_get("dalvik.vm.heapsize", heapsizeOptsBuf+4, "16m");
36. //LOGI("Heap size: %s", heapsizeOptsBuf);
37. opt.optionString = heapsizeOptsBuf;
38. mOptions.add(opt);
39. ......
40.
41. if (checkJni) {
42. /* extended JNI checking */
43. opt.optionString = "-Xcheck:jni";
44. mOptions.add(opt);
45.
46. ......
47. }
48. ......
49.
50. if (executionMode == kEMIntPortable) {
51. opt.optionString = "-Xint:portable";
52. mOptions.add(opt);
53. } else if (executionMode == kEMIntFast) {
54. opt.optionString = "-Xint:fast";
55. mOptions.add(opt);
56. #if defined(WITH_JIT)
57. } else if (executionMode == kEMJitCompiler) {
58. opt.optionString = "-Xint:jit";
59. mOptions.add(opt);
60. #endif
61. }
62. ......
63.
64. if (stackTraceFileBuf[0] != '\0') {
65. static const char* stfOptName = "-Xstacktracefile:";
66.
67. stackTraceFile = (char*) malloc(strlen(stfOptName) +
68. strlen(stackTraceFileBuf) +1);
69. strcpy(stackTraceFile, stfOptName);
70. strcat(stackTraceFile, stackTraceFileBuf);
71. opt.optionString = stackTraceFile;
72. mOptions.add(opt);
73. }
74. ......
75.
76. initArgs.options = mOptions.editArray();
77. initArgs.nOptions = mOptions.size();
78. ......
79.
80. /*
81. * Initialize the VM.
82. *
83. * The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
84. * If this call succeeds, the VM is ready, and we can start issuing
85. * JNI calls.
86. */
87. if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
88. LOGE("JNI_CreateJavaVM failed\n");
89. goto bail;
90. }
91.
92. result = 0;
93.
94. bail:
95. free(stackTraceFile);
96. return result;
97. }
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)
{
int result = -1;
JavaVMInitArgs initArgs;
JavaVMOption opt;
......
property_get("dalvik.vm.checkjni", propBuf, "");
if (strcmp(propBuf, "true") == 0) {
checkJni = true;
} else if (strcmp(propBuf, "false") != 0) {
/* property is neither true nor false; fall back on kernel parameter */
property_get("ro.kernel.android.checkjni", propBuf, "");
if (propBuf[0] == '1') {
checkJni = true;
}
}
......
property_get("dalvik.vm.execution-mode", propBuf, "");
if (strcmp(propBuf, "int:portable") == 0) {
executionMode = kEMIntPortable;
} else if (strcmp(propBuf, "int:fast") == 0) {
executionMode = kEMIntFast;
#if defined(WITH_JIT)
} else if (strcmp(propBuf, "int:jit") == 0) {
executionMode = kEMJitCompiler;
#endif
}
property_get("dalvik.vm.stack-trace-file", stackTraceFileBuf, "");
......
strcpy(heapsizeOptsBuf, "-Xmx");
property_get("dalvik.vm.heapsize", heapsizeOptsBuf+4, "16m");
//LOGI("Heap size: %s", heapsizeOptsBuf);
opt.optionString = heapsizeOptsBuf;
mOptions.add(opt);
......
if (checkJni) {
/* extended JNI checking */
opt.optionString = "-Xcheck:jni";
mOptions.add(opt);
......
}
......
if (executionMode == kEMIntPortable) {
opt.optionString = "-Xint:portable";
mOptions.add(opt);
} else if (executionMode == kEMIntFast) {
opt.optionString = "-Xint:fast";
mOptions.add(opt);
#if defined(WITH_JIT)
} else if (executionMode == kEMJitCompiler) {
opt.optionString = "-Xint:jit";
mOptions.add(opt);
#endif
}
......
if (stackTraceFileBuf[0] != '\0') {
static const char* stfOptName = "-Xstacktracefile:";
stackTraceFile = (char*) malloc(strlen(stfOptName) +
strlen(stackTraceFileBuf) +1);
strcpy(stackTraceFile, stfOptName);
strcat(stackTraceFile, stackTraceFileBuf);
opt.optionString = stackTraceFile;
mOptions.add(opt);
}
......
initArgs.options = mOptions.editArray();
initArgs.nOptions = mOptions.size();
......
/*
* Initialize the VM.
*
* The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
* If this call succeeds, the VM is ready, and we can start issuing
* JNI calls.
*/
if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
LOGE("JNI_CreateJavaVM failed\n");
goto bail;
}
result = 0;
bail:
free(stackTraceFile);
return result;
}
这个函数定义在文件frameworks/base/core/jni/AndroidRuntime.cpp中。
在启动Dalvik虚拟机的时候,可以指定一系列的选项,这些选项可以通过特定的系统属性来指定。下面我们就简单了解几个可能有用的选项。
1. -Xcheck:jni:用来启动JNI方法检查。我们在C/C++代码中,可以修改Java对象的成员变量或者调用Java对象的成员函数。加了-Xcheck:jni选项之后,就可以对要访问的Java对象的成员变量或者成员函数进行合法性检查,例如,检查类型是否匹配。我们可以通过dalvik.vm.checkjni或者ro.kernel.android.checkjni这两个系统属性来指定是否要启用-Xcheck:jni选项。注意,加了-Xcheck:jni选项之后,会使用得JNI方法执行变慢。
2. -Xint:portable,-Xint:fast,-Xint:jit:用来指定Dalvik虚拟机的执行模式。Dalvik虚拟机支持三种运行模式,分别是Portable、Fast和Jit。Portable是指Dalvik虚拟机以可移植的方式来进行编译,也就是说,编译出来的虚拟机可以在任意平台上运行。Fast是针对当前平台对Dalvik虚拟机进行编译,这样编译出来的Dalvik虚拟机可以进行特殊的优化,从而使得它能更快地运行程序。Jit不是解释执行代码,而是将代码动态编译成本地语言后再执行。我们可以通过dalvik.vm.execution-mode系统属笥来指定Dalvik虚拟机的解释模式。
3. -Xstacktracefile:用来指定调用堆栈输出文件。Dalvik虚拟机接收到SIGQUIT(Ctrl-\或者kill -3)信号之后,会将所有线程的调用堆栈输出来,默认是输出到日志里面。指定了-Xstacktracefile选项之后,就可以将线程的调用堆栈输出到指定的文件中去。我们可以通过dalvik.vm.stack-trace-file系统属性来指定调用堆栈输出文件。
4. -Xmx:用来指定Java对象堆的最大值。Dalvik虚拟机的Java对象堆的默认最大值是16M,不过我们可以通过dalvik.vm.heapsize系统属性来指定为其它值。
更多的Dalvik虚拟机启动选项,可以参考Controlling the Embedded VM一文。
设置好Dalvik虚拟机的启动选项之后,AndroidRuntime的成员函数startVm就会调用另外一个函数JNI_CreateJavaVM来创建以及初始化一个Dalvik虚拟机实例。
Step 3. JNI_CreateJavaVM
[cpp]view plaincopyprint?
1. /*
2. * Create a new VM instance.
3. *
4. * The current thread becomes the main VM thread. We return immediately,
5. * which effectively means the caller is executing in a native method.
6. */
7. jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args)
8. {
9. const JavaVMInitArgs* args = (JavaVMInitArgs*) vm_args;
10. JNIEnvExt* pEnv = NULL;
11. JavaVMExt* pVM = NULL;
12. const char** argv;
13. int argc = 0;
14. ......
15.
16. /* zero globals; not strictly necessary the first time a VM is started */
17. memset(&gDvm, 0, sizeof(gDvm));
18.
19. /*
20. * Set up structures for JNIEnv and VM.
21. */
22. //pEnv = (JNIEnvExt*) malloc(sizeof(JNIEnvExt));
23. pVM = (JavaVMExt*) malloc(sizeof(JavaVMExt));
24.
25. memset(pVM, 0, sizeof(JavaVMExt));
26. pVM->funcTable = &gInvokeInterface;
27. pVM->envList = pEnv;
28. ......
29.
30. argv = (const char**) malloc(sizeof(char*) * (args->nOptions));
31. memset(argv, 0, sizeof(char*) * (args->nOptions));
32. ......
33.
34. /*
35. * Convert JNI args to argv.
36. *
37. * We have to pull out vfprintf/exit/abort, because they use the
38. * "extraInfo" field to pass function pointer "hooks" in. We also
39. * look for the -Xcheck:jni stuff here.
40. */
41. for (i = 0; i < args->nOptions; i++) {
42. ......
43. }
44.
45. ......
46.
47. /* set this up before initializing VM, so it can create some JNIEnvs */
48. gDvm.vmList = (JavaVM*) pVM;
49.
50. /*
51. * Create an env for main thread. We need to have something set up
52. * here because some of the class initialization we do when starting
53. * up the VM will call into native code.
54. */
55. pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);
56.
57. /* initialize VM */
58. gDvm.initializing = true;
59. if (dvmStartup(argc, argv, args->ignoreUnrecognized, (JNIEnv*)pEnv) != 0) {
60. free(pEnv);
61. free(pVM);
62. goto bail;
63. }
64.
65. /*
66. * Success! Return stuff to caller.
67. */
68. dvmChangeStatus(NULL, THREAD_NATIVE);
69. *p_env = (JNIEnv*) pEnv;
70. *p_vm = (JavaVM*) pVM;
71. result = JNI_OK;
72.
73. bail:
74. gDvm.initializing = false;
75. if (result == JNI_OK)
76. LOGV("JNI_CreateJavaVM succeeded\n");
77. else
78. LOGW("JNI_CreateJavaVM failed\n");
79. free(argv);
80. return result;
81. }
/*
* Create a new VM instance.
*
* The current thread becomes the main VM thread. We return immediately,
* which effectively means the caller is executing in a native method.
*/
jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args)
{
const JavaVMInitArgs* args = (JavaVMInitArgs*) vm_args;
JNIEnvExt* pEnv = NULL;
JavaVMExt* pVM = NULL;
const char** argv;
int argc = 0;
......
/* zero globals; not strictly necessary the first time a VM is started */
memset(&gDvm, 0, sizeof(gDvm));
/*
* Set up structures for JNIEnv and VM.
*/
//pEnv = (JNIEnvExt*) malloc(sizeof(JNIEnvExt));
pVM = (JavaVMExt*) malloc(sizeof(JavaVMExt));
memset(pVM, 0, sizeof(JavaVMExt));
pVM->funcTable = &gInvokeInterface;
pVM->envList = pEnv;
......
argv = (const char**) malloc(sizeof(char*) * (args->nOptions));
memset(argv, 0, sizeof(char*) * (args->nOptions));
......
/*
* Convert JNI args to argv.
*
* We have to pull out vfprintf/exit/abort, because they use the
* "extraInfo" field to pass function pointer "hooks" in. We also
* look for the -Xcheck:jni stuff here.
*/
for (i = 0; i < args->nOptions; i++) {
......
}
......
/* set this up before initializing VM, so it can create some JNIEnvs */
gDvm.vmList = (JavaVM*) pVM;
/*
* Create an env for main thread. We need to have something set up
* here because some of the class initialization we do when starting
* up the VM will call into native code.
*/
pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);
/* initialize VM */
gDvm.initializing = true;
if (dvmStartup(argc, argv, args->ignoreUnrecognized, (JNIEnv*)pEnv) != 0) {
free(pEnv);
free(pVM);
goto bail;
}
/*
* Success! Return stuff to caller.
*/
dvmChangeStatus(NULL, THREAD_NATIVE);
*p_env = (JNIEnv*) pEnv;
*p_vm = (JavaVM*) pVM;
result = JNI_OK;
bail:
gDvm.initializing = false;
if (result == JNI_OK)
LOGV("JNI_CreateJavaVM succeeded\n");
else
LOGW("JNI_CreateJavaVM failed\n");
free(argv);
return result;
}
这个函数定义在文件dalvik/vm/Jni.c中。
JNI_CreateJavaVM主要完成以下四件事情。
1.为当前进程创建一个Dalvik虚拟机实例,即一个JavaVMExt对象。
2.为当前线程创建和初始化一个JNI环境,即一个JNIEnvExt对象,这是通过调用函数dvmCreateJNIEnv来完成的。
3.将参数vm_args所描述的Dalvik虚拟机启动选项拷贝到变量argv所描述的一个字符串数组中去,并且调用函数dvmStartup来初始化前面所创建的Dalvik虚拟机实例。
4.调用函数dvmChangeStatus将当前线程的状态设置为正在执行NATIVE代码,并且将面所创建和初始化好的JavaVMExt对象和JNIEnvExt对象通过输出参数p_vm和p_env返回给调用者。
gDvm是一个类型为DvmGlobals的全局变量,用来收集当前进程所有虚拟机相关的信息,其中,它的成员变量vmList指向的就是当前进程中的Dalvik虚拟机实例,即一个JavaVMExt对象。以后每当需要访问当前进程中的Dalvik虚拟机实例时,就可以通过全局变量gDvm的成员变量vmList来获得,避免了在函数之间传递该Dalvik虚拟机实例。
每一个Dalvik虚拟机实例都有一个函数表,保存在对应的JavaVMExt对象的成员变量funcTable中,而这个函数表又被指定为gInvokeInterface。gInvokeInterface是一个类型为JNIInvokeInterface的结构体,它定义在文件dalvik/vm/Jni.c中,如下所示:
[cpp]view plaincopyprint?
1. static const struct JNIInvokeInterface gInvokeInterface = {
2. NULL,
3. NULL,
4. NULL,
5.
6. DestroyJavaVM,
7. AttachCurrentThread,
8. DetachCurrentThread,
9.
10. GetEnv,
11.
12. AttachCurrentThreadAsDaemon,
13. };
static const struct JNIInvokeInterface gInvokeInterface = {
NULL,
NULL,
NULL,
DestroyJavaVM,
AttachCurrentThread,
DetachCurrentThread,
GetEnv,
AttachCurrentThreadAsDaemon,
};
有了这个Dalvik虚拟机函数表之后,我们就可以将当前线程Attach或者Detach到Dalvik虚拟机中去,或者销毁当前进程的Dalvik虚拟机等。
每一个Dalvik虚拟机实例还有一个JNI环境列表,保存在对应的JavaVMExt对象的成员变量envList中。注意,JavaVMExt对象的成员变量envList描述的是一个JNIEnvExt列表,其中,每一个Attach到Dalvik虚拟机中去的线程都有一个对应的JNIEnvExt,用来描述它的JNI环境。有了这个JNI环境之后,我们才可以在Java函数和C/C++函数之间互相调用。
每一个JNIEnvExt对象都有两个成员变量prev和next,它们均是一个JNIEnvExt指针,分别指向前一个JNIEnvExt对象和后一个JNIEnvExt对象,也就是说,每一个Dalvik虚拟机实例的成员变量envList描述的是一个双向JNIEnvExt列表,其中,列表中的第一个JNIEnvExt对象描述的是主线程的JNI环境。
上述提到的DvmGlobals结构体定义文件dalvik/vm/Globals.h中,JNIInvokeInterface结构体定义在文件dalvik/libnativehelper/include/nativehelper/jni.h中,JavaVMExt和JNIEnvExt结构体定义在文件dalvik/vm/JniInternal.h中。
接下来,我们接下来就继续分析函数dvmCreateJNIEnv和函数dvmStartup的实现,以便可以了解JNI环境的创建和初始化过程,以及Dalvik虚拟机的虚拟机初始化过程。
Step 4. dvmCreateJNIEnv
[cpp]view plaincopyprint?
1. /*
2. * Create a new JNIEnv struct and add it to the VM's list.
3. *
4. * "self" will be NULL for the main thread, since the VM hasn't started
5. * yet; the value will be filled in later.
6. */
7. JNIEnv* dvmCreateJNIEnv(Thread* self)
8. {
9. JavaVMExt* vm = (JavaVMExt*) gDvm.vmList;
10. JNIEnvExt* newEnv;
11.
12. ......
13.
14. newEnv = (JNIEnvExt*) calloc(1, sizeof(JNIEnvExt));
15. newEnv->funcTable = &gNativeInterface;
16. newEnv->vm = vm;
17.
18. ......
19.
20. if (self != NULL) {
21. dvmSetJniEnvThreadId((JNIEnv*) newEnv, self);
22. assert(newEnv->envThreadId != 0);
23. } else {
24. /* make it obvious if we fail to initialize these later */
25. newEnv->envThreadId = 0x77777775;
26. newEnv->self = (Thread*) 0x77777779;
27. }
28.
29. ......
30.
31. /* insert at head of list */
32. newEnv->next = vm->envList;
33. assert(newEnv->prev == NULL);
34. if (vm->envList == NULL) // rare, but possible
35. vm->envList = newEnv;
36. else
37. vm->envList->prev = newEnv;
38. vm->envList = newEnv;
39.
40. ......
41.
42. return (JNIEnv*) newEnv;
43. }
/*
* Create a new JNIEnv struct and add it to the VM's list.
*
* "self" will be NULL for the main thread, since the VM hasn't started
* yet; the value will be filled in later.
*/
JNIEnv* dvmCreateJNIEnv(Thread* self)
{
JavaVMExt* vm = (JavaVMExt*) gDvm.vmList;
JNIEnvExt* newEnv;
......
newEnv = (JNIEnvExt*) calloc(1, sizeof(JNIEnvExt));
newEnv->funcTable = &gNativeInterface;
newEnv->vm = vm;
......
if (self != NULL) {
dvmSetJniEnvThreadId((JNIEnv*) newEnv, self);
assert(newEnv->envThreadId != 0);
} else {
/* make it obvious if we fail to initialize these later */
newEnv->envThreadId = 0x77777775;
newEnv->self = (Thread*) 0x77777779;
}
......
/* insert at head of list */
newEnv->next = vm->envList;
assert(newEnv->prev == NULL);
if (vm->envList == NULL) // rare, but possible
vm->envList = newEnv;
else
vm->envList->prev = newEnv;
vm->envList = newEnv;
......
return (JNIEnv*) newEnv;
}
这个函数定义在文件dalvik/vm/Jni.c中。
函数dvmCreateJNIEnv主要是执行了以下三个操作:
1.创建一个JNIEnvExt对象,用来描述一个JNI环境,并且设置这个JNIEnvExt对象的宿主Dalvik虚拟机,以及所使用的本地接口表,即设置这个JNIEnvExt对象的成员变量funcTable和vm。这里的宿主Dalvik虚拟机即为当前进程的Dalvik虚拟机,它保存在全局变量gDvm的成员变量vmList中。本地接口表由全局变量gNativeInterface来描述。
2.参数self描述的是前面创建的JNIEnvExt对象要关联的线程,可以通过调用函数dvmSetJniEnvThreadId来将它们关联起来。注意,当参数self的值等于NULL的时候,就表示前面的JNIEnvExt对象是要与主线程关联的,但是要等到后面再关联,因为现在用来描述主线程的Thread对象还没有准备好。通过将一个JNIEnvExt对象的成员变量envThreadId和self的值都设置为0x77777779来表示它还没有与线程关联。
3.在一个Dalvik虚拟机里面,可以运行多个线程。所有关联有JNI环境的线程都有一个对应的JNIEnvExt对象,这些JNIEnvExt对象相互连接在一起保存在用来描述其宿主Dalvik虚拟机的一个JavaVMExt对象的成员变量envList中。因此,前面创建的JNIEnvExt对象需要连接到其宿主Dalvik虚拟机的JavaVMExt链表中去。
gNativeInterface是一个类型为JNINativeInterface的结构体,它定义在文件dalvik/vm/Jni.c,如下所示:
[cpp]view plaincopyprint?
1. static const struct JNINativeInterface gNativeInterface = {
2. ......
3.
4. FindClass,
5.
6. ......
7.
8. GetMethodID,
9.
10. ......
11.
12. CallObjectMethod,
13.
14. ......
15.
16. GetFieldID,
17.
18. ......
19.
20. SetIntField,
21.
22. ......
23.
24. RegisterNatives,
25. UnregisterNatives,
26.
27. ......
28.
29. GetJavaVM,
30.
31. ......
32. };
static const struct JNINativeInterface gNativeInterface = {
......
FindClass,
......
GetMethodID,
......
CallObjectMethod,
......
GetFieldID,
......
SetIntField,
......
RegisterNatives,
UnregisterNatives,
......
GetJavaVM,
......
};
从gNativeInterface的定义就可以看出,结构体JNINativeInterface用来描述一个本地接口表。当我们需要在C/C++代码在中调用Java函数,就要用到这个本地接口表,例如:
1.调用函数FindClass可以找到指定的Java类;
2.调用函数GetMethodID可以获得一个Java类的成员函数,并且可以通过类似CallObjectMethod函数来间接调用它;
3.调用函数GetFieldID可以获得一个Java类的成员变量,并且可以通过类似SetIntField的函数来设置它的值;
4.调用函数RegisterNatives和UnregisterNatives可以注册和反注册JNI方法到一个Java类中去,以便可以在Java函数中调用;
5.调用函数GetJavaVM可以获得当前进程中的Dalvik虚拟机实例。
事实上,结构体JNINativeInterface定义的可以在C/C++代码中调用的函数非常多,具体可以参考它在dalvik\libnativehelper\include\nativehelper\jni.h文件中的定义。
这一步执行完成之后,返回到前面的Step 3中,即函数JNI_CreateJavaVM中,接下来就会继续调用函数dvmStartup来初始化前面所创建的Dalvik虚拟机实例。
Step 5. dvmStartup
这个函数定义在文件dalvik/vm/Init.c中,用来初始化Dalvik虚拟机,我们分段来阅读:
[cpp]view plaincopyprint?
1. /*
2. * VM initialization. Pass in any options provided on the command line.
3. * Do not pass in the class name or the options for the class.
4. *
5. * Returns 0 on success.
6. */
7. int dvmStartup(int argc, const char* const argv[], bool ignoreUnrecognized,
8. JNIEnv* pEnv)
9. {
10. int i, cc;
11.
12. ......
13.
14. setCommandLineDefaults();
15.
16. /* prep properties storage */
17. if (!dvmPropertiesStartup(argc))
18. goto fail;
19.
20. /*
21. * Process the option flags (if any).
22. */
23. cc = dvmProcessOptions(argc, argv, ignoreUnrecognized);
24. if (cc != 0) {
25. ......
26. goto fail;
27. }
/*
* VM initialization. Pass in any options provided on the command line.
* Do not pass in the class name or the options for the class.
*
* Returns 0 on success.
*/
int dvmStartup(int argc, const char* const argv[], bool ignoreUnrecognized,
JNIEnv* pEnv)
{
int i, cc;
......
setCommandLineDefaults();
/* prep properties storage */
if (!dvmPropertiesStartup(argc))
goto fail;
/*
* Process the option flags (if any).
*/
cc = dvmProcessOptions(argc, argv, ignoreUnrecognized);
if (cc != 0) {
......
goto fail;
}
这段代码用来处理Dalvik虚拟机的启动选项,这些启动选项保存在参数argv中,并且个数等于argc。在处理这些启动选项之前,还会执行以下两个操作:
1.调用函数setCommandLineDefaults来给Dalvik虚拟机设置默认参数,因为启动选项不一定会指定Dalvik虚拟机的所有属性。
2.调用函数dvmPropertiesStartup来分配足够的内存空间来容纳由参数argv和argc所描述的启动选项。
完成以上两个操作之后,就可以调用函数dvmProcessOptions来处理参数argv和argc所描述的启动选项了,也就是根据这些选项值来设置Dalvik虚拟机的属性,例如,设置Dalvik虚拟机的Java对象堆的最大值。
在上述代码中,函数setCommandLineDefaults和dvmPropertiesStartup定义在文件dalvik/vm/Init.c中,函数dvmPropertiesStartup定义在文件dalvik/vm/Properties.c中。
我们继续往下阅读代码:
[cpp]view plaincopyprint?
1. /* configure signal handling */
2. if (!gDvm.reduceSignals)
3. blockSignals();
/* configure signal handling */
if (!gDvm.reduceSignals)
blockSignals();
如果我们没有在Dalvik虚拟机的启动选项中指定-Xrs,那么gDvm.reduceSignals的值就会被设置为false,表示要在当前线程中屏蔽掉SIGQUIT信号。在这种情况下,会有一个线程专门用来处理SIGQUIT信号。这个线程在接收到SIGQUIT信号的时候,就会将各个线程的调用堆栈打印出来,因此,这个线程又称为dump-stack-trace线程。
屏蔽当前线程的SIGQUIT信号是通过调用函数blockSignals来实现的,这个函数定义在文件dalvik/vm/Init.c中。
我们继续往下阅读代码:
[cpp]view plaincopyprint?
1. /*
2. * Initialize components.
3. */
4. if (!dvmAllocTrackerStartup())
5. goto fail;
6. if (!dvmGcStartup())
7. goto fail;
8. if (!dvmThreadStartup())
9. goto fail;
10. if (!dvmInlineNativeStartup())
11. goto fail;
12. if (!dvmVerificationStartup())
13. goto fail;
14. if (!dvmRegisterMapStartup())
15. goto fail;
16. if (!dvmInstanceofStartup())
17. goto fail;
18. if (!dvmClassStartup())
19. goto fail;
20. if (!dvmThreadObjStartup())
21. goto fail;
22. if (!dvmExceptionStartup())
23. goto fail;
24. if (!dvmStringInternStartup())
25. goto fail;
26. if (!dvmNativeStartup())
27. goto fail;
28. if (!dvmInternalNativeStartup())
29. goto fail;
30. if (!dvmJniStartup())
31. goto fail;
32. if (!dvmReflectStartup())
33. goto fail;
34. if (!dvmProfilingStartup())
35. goto fail;
/*
* Initialize components.
*/
if (!dvmAllocTrackerStartup())
goto fail;
if (!dvmGcStartup())
goto fail;
if (!dvmThreadStartup())
goto fail;
if (!dvmInlineNativeStartup())
goto fail;
if (!dvmVerificationStartup())
goto fail;
if (!dvmRegisterMapStartup())
goto fail;
if (!dvmInstanceofStartup())
goto fail;
if (!dvmClassStartup())
goto fail;
if (!dvmThreadObjStartup())
goto fail;
if (!dvmExceptionStartup())
goto fail;
if (!dvmStringInternStartup())
goto fail;
if (!dvmNativeStartup())
goto fail;
if (!dvmInternalNativeStartup())
goto fail;
if (!dvmJniStartup())
goto fail;
if (!dvmReflectStartup())
goto fail;
if (!dvmProfilingStartup())
goto fail;
这段代码用来初始化Dalvik虚拟机的各个子模块,接下来我们就分别描述。
1. dvmAllocTrackerStartup
这个函数定义在文件dalvik/vm/AllocTracker.c中,用来初始化Davlik虚拟机的对象分配记录子模块,这样我们就可以通过DDMS工具来查看Davlik虚拟机的对象分配情况。
2. dvmGcStartup
这个函数定义在文件dalvik/vm/alloc/Alloc.c中,用来初始化Davlik虚拟机的垃圾收集( GC)子模块。
3. dvmThreadStartup
这个函数定义在文件dalvik/vm/Thread.c中,用来初始化Davlik虚拟机的线程列表、为主线程创建一个Thread对象以及为主线程初始化执行环境。Davlik虚拟机中的所有线程均是本地操作系统线程。在Linux系统中,一般都是使用pthread库来创建和管理线程的,Android系统也不例外,也就是说,Davlik虚拟机中的每一个线程均是一个pthread线程。注意,Davlik虚拟机中的每一个线程均用一个Thread结构体来描述,这些Thread结构体组织在一个列表中,因此,这里要先对它进行初始化。
4. dvmInlineNativeStartup
这个函数定义在文件dalvik/vm/InlineNative.c中,用来初始化Davlik虚拟机的内建Native函数表。这些内建Native函数主要是针对java.Lang.String、java.Lang.Math、java.Lang.Float和java.Lang.Double类的,用来替换这些类的某些成员函数原来的实现(包括Java实现和Native实现)。例如,当我们调用java.Lang.String类的成员函数compareTo来比较两个字符串的大小时,实际执行的是由Davlik虚拟机提供的内建函数javaLangString_compareTo(同样是定义在文件dalvik/vm/InlineNative.c中)。在提供有__memcmp16函数的系统中,函数javaLangString_compareTo会利用它来直接比较两个字符串的大小。由于函数__memcmp16是用优化过的汇编语言的来实现的,它的效率会更高。
5. dvmVerificationStartup
这个函数定义在文件dalvik/vm/analysis/DexVerify.c中,用来初始化Dex文件验证器。Davlik虚拟机与Java虚拟机一样,在加载一个类文件的时候,一般需要验证它的合法性,也就是验证文件中有没有非法的指令或者操作等。
6. dvmRegisterMapStartup
这个函数定义在文件dalvik/vm/analysis/RegisterMap.c中,用来初始化寄存器映射集(Register Map)子模块。Davlik虚拟机支持精确垃圾收集(Exact GC或者Precise GC),也就是说,在进行垃圾收集的时候,Davlik虚拟机可以准确地判断当前正在使用的每一个寄存器里面保存的是对象使用还是非对象引用。对于对象引用,意味被引用的对象现在还不可以回收,因此,就可以进行精确的垃圾收集。
为了帮助垃圾收集器准备地判断寄存器保存的是对象引用还是非对象引用,Davlik虚拟机在验证了一个类之后,还会为它的每一个成员函数生成一个寄存器映射集。寄存器映射集记录了类成员函数在每一个GC安全点(Safe Point)中的寄存器使用情况,也就是记录每一个寄存器里面保存的是对象引用还是非对象引用。由于垃圾收集器一定是在GC安全点进行垃圾收集的,因此,根据每一个GC安全点的寄存器映射集,就可以准确地知道对象的引用情况,从而可以确定哪些可以回收,哪些对象还不可以回收。
7. dvmInstanceofStartup
这个函数定义在文件dalvik/vm/oo/TypeCheck.c中,用来初始化instanceof操作符子模块。在使用instanceof操作符来判断一个对象A是否是一个类B的实例时,Davlik虚拟机需要检查类B是否是从对象A的声明类继承下来的。由于这个检查的过程比较耗时,Davlik虚拟机在内部使用一个缓冲,用来记录第一次两个类之间的instanceof操作结果,这样后面再碰到相同的instanceof操作时,就可以快速地得到结果。
8. dvmClassStartup
这个函数定义在文件dalvik/vm/oo/Class.c中,用来初始化启动类加载器(Bootstrap Class Loader),同时还会初始化java.lang.Class类。启动类加载器是用来加载Java核心类的,用来保证安全性,即保证加载的Java核心类是合法的。
9. dvmThreadObjStartup
这个函数定义在文件dalvik/vm/Thread.c中,用来加载与线程相关的类,即java.lang.Thread、java.lang.VMThread和java.lang.ThreadGroup。
10. dvmExceptionStartup
这个函数定义在文件dalvik/vm/Exception.c中,用来加载与异常相关的类,即java.lang.Throwable、java.lang.RuntimeException、java.lang.StackOverflowError、java.lang.Error、java.lang.StackTraceElement和java.lang.StackTraceElement类。
11. dvmStringInternStartup
这个函数定义在文件dalvik/vm/Intern.c中,用来初始化java.lang.String类内部私有一个字符串池,这样当Dalvik虚拟机运行起来之后,我们就可以调用java.lang.String类的成员函数intern来访问这个字符串池里面的字符串。
12. dvmNativeStartup
这个函数定义在文件dalvik/vm/Native.c中,用来初始化Native Shared Object库加载表,也就是SO库加载表。这个加载表是用来描述当前进程有哪些SO文件已经被加载过了。
13. dvmInternalNativeStartup
这个函数定义在文件dalvik/vm/native/InternalNative.c中,用来初始化一个内部Native函数表。所有需要直接访问Dalvik虚拟机内部函数或者数据结构的Native函数都定义在这张表中,因为它们如果定义在外部的其它SO文件中,就无法直接访问Dalvik虚拟机的内部函数或者数据结构。例如,前面提到的java.lang.String类的成员函数intent,由于它要访问Dalvik虚拟机内部的一个私有字符串池,因此,它所对应的Native函数就要在Dalvik虚拟机内部实现。
14. dvmJniStartup
这个函数定义在文件dalvik/vm/Jni.c中,用来初始化全局引用表,以及加载一些与Direct Buffer相关的类,如DirectBuffer、PhantomReference和ReferenceQueue等。
我们在一个JNI方法中,可能会需要访问一些Java对象,这样就需要通知GC,这些Java对象现在正在被Native Code引用,不能回收。这些被Native Code引用的Java对象就会被记录在一个全局引用表中,具体的做法就是调用JNI环境对象(JNIEnv)的成员函数NewLocalRef/DeleteLocalRef和NewGlobalRef/DeleteGlobalRef等来显式地引用或者释放Java对象。
有时候我们需要在Java代码中,直接在Native层分配内存,也就直接使用malloc来分配内存。这些Native内存不同于在Java堆中分配的内存,区别在于前者需要不接受GC管理,而后者接受GC管理。这些直接在Native层分配的内存有什么用呢?考虑一个场景,我们需要在Java代码中从一个IO设备中读取数据。从IO设备读取数据意味着要调用由本地操作系统提供的read接口来实现。这样我们就有两种做法。第一种做法在Native层临时分配一个缓冲区,用来保存从IO设备read回来的数据,然后再将这个数据拷贝到Java层中去,也就是拷贝到Java堆去使用。第二种做法是在Java层创建一个对象,这个对象在Native层直接关联有一块内存,从IO设备read回来的数据就直接保存这块内存中。第二种方法和第一种方法相比,减少了一次内存拷贝,因而可以提高性能。
我们将这种能够直接在Native层中分配内存的Java对象就称为DirectBuffer。由于DirectBuffer使用的内存是不接受GC管理的,因此,我们就需要通过其它的方式来管理它们。具体做法就是为每一个DirectBuffer对象创建一个PhantomReference引用。注意,DirectBuffer对象本身是一个Java对象,它是接受GC管理的。当GC准备回收一个DirectBuffer对象时,如果发现它还有PhantomReference引用,那就会在回收它之前,把相应的PhantomReference引用加入到与之关联的一个ReferenceQueue队列中去。这样我们就可以通过判断一个DirectBuffer对象的PhantomReference引用是否已经加入到一个相关的ReferenceQueue队列中。如果已经加入了的话,那么就可以在该DirectBuffer对象被回收之前,释放掉之前为它在Native层分配的内存。
15. dvmReflectStartup
这个函数定义在文件dalvik/vm/reflect/Reflect.c中,用来加载反射相关的类,如java.lang.reflect.AccessibleObject、java.lang.reflect.Constructor、java.lang.reflect.Field、java.lang.reflect.Method和java.lang.reflect.Proxy等。
16. dvmProfilingStartup
这个函数定义在文件dalvik/vm/Profile.c,用来初始化Dalvik虚拟机的性能分析子模块,以及加载dalvik.system.VMDebug类等。
Dalvik虚拟机的各个子模块初始化完成之后,我们继续往下阅读代码:
[cpp]view plaincopyprint?
1. /* make sure we got these [can this go away?] */
2. assert(gDvm.classJavaLangClass != NULL);
3. assert(gDvm.classJavaLangObject != NULL);
4. //assert(gDvm.classJavaLangString != NULL);
5. assert(gDvm.classJavaLangThread != NULL);
6. assert(gDvm.classJavaLangVMThread != NULL);
7. assert(gDvm.classJavaLangThreadGroup != NULL);
8.
9. /*
10. * Make sure these exist. If they don't, we can return a failure out
11. * of main and nip the whole thing in the bud.
12. */
13. static const char* earlyClasses[] = {
14. "Ljava/lang/InternalError;",
15. "Ljava/lang/StackOverflowError;",
16. "Ljava/lang/UnsatisfiedLinkError;",
17. "Ljava/lang/NoClassDefFoundError;",
18. NULL
19. };
20. const char** pClassName;
21. for (pClassName = earlyClasses; *pClassName != NULL; pClassName++) {
22. if (dvmFindSystemClassNoInit(*pClassName) == NULL)
23. goto fail;
24. }
/* make sure we got these [can this go away?] */
assert(gDvm.classJavaLangClass != NULL);
assert(gDvm.classJavaLangObject != NULL);
//assert(gDvm.classJavaLangString != NULL);
assert(gDvm.classJavaLangThread != NULL);
assert(gDvm.classJavaLangVMThread != NULL);
assert(gDvm.classJavaLangThreadGroup != NULL);
/*
* Make sure these exist. If they don't, we can return a failure out
* of main and nip the whole thing in the bud.
*/
static const char* earlyClasses[] = {
"Ljava/lang/InternalError;",
"Ljava/lang/StackOverflowError;",
"Ljava/lang/UnsatisfiedLinkError;",
"Ljava/lang/NoClassDefFoundError;",
NULL
};
const char** pClassName;
for (pClassName = earlyClasses; *pClassName != NULL; pClassName++) {
if (dvmFindSystemClassNoInit(*pClassName) == NULL)
goto fail;
}
这段代码检查java.lang.Class、java.lang.Object、java.lang.Thread、java.lang.VMThread和java.lang.ThreadGroup这五个核心类经过前面的初始化操作后已经得到加载,并且确保系统中存在java.lang.InternalError、java.lang.StackOverflowError、java.lang.UnsatisfiedLinkError和java.lang.NoClassDefFoundError这四个核心类。
我们继续往下阅读代码:
[cpp]view plaincopyprint?
1. /*
2. * Miscellaneous class library validation.
3. */
4. if (!dvmValidateBoxClasses())
5. goto fail;
6.
7. /*
8. * Do the last bits of Thread struct initialization we need to allow
9. * JNI calls to work.
10. */
11. if (!dvmPrepMainForJni(pEnv))
12. goto fail;
13.
14. /*
15. * Register the system native methods, which are registered through JNI.
16. */
17. if (!registerSystemNatives(pEnv))
18. goto fail;
19.
20. /*
21. * Do some "late" initialization for the memory allocator. This may
22. * allocate storage and initialize classes.
23. */
24. if (!dvmCreateStockExceptions())
25. goto fail;
26.
27. /*
28. * At this point, the VM is in a pretty good state. Finish prep on
29. * the main thread (specifically, create a java.lang.Thread object to go
30. * along with our Thread struct). Note we will probably be executing
31. * some interpreted class initializer code in here.
32. */
33. if (!dvmPrepMainThread())
34. goto fail;
35.
36. /*
37. * Make sure we haven't accumulated any tracked references. The main
38. * thread should be starting with a clean slate.
39. */
40. if (dvmReferenceTableEntries(&dvmThreadSelf()->internalLocalRefTable) != 0)
41. {
42. LOGW("Warning: tracked references remain post-initialization\n");
43. dvmDumpReferenceTable(&dvmThreadSelf()->internalLocalRefTable, "MAIN");
44. }
45.
46. /* general debugging setup */
47. if (!dvmDebuggerStartup())
48. goto fail;
/*
* Miscellaneous class library validation.
*/
if (!dvmValidateBoxClasses())
goto fail;
/*
* Do the last bits of Thread struct initialization we need to allow
* JNI calls to work.
*/
if (!dvmPrepMainForJni(pEnv))
goto fail;
/*
* Register the system native methods, which are registered through JNI.
*/
if (!registerSystemNatives(pEnv))
goto fail;
/*
* Do some "late" initialization for the memory allocator. This may
* allocate storage and initialize classes.
*/
if (!dvmCreateStockExceptions())
goto fail;
/*
* At this point, the VM is in a pretty good state. Finish prep on
* the main thread (specifically, create a java.lang.Thread object to go
* along with our Thread struct). Note we will probably be executing
* some interpreted class initializer code in here.
*/
if (!dvmPrepMainThread())
goto fail;
/*
* Make sure we haven't accumulated any tracked references. The main
* thread should be starting with a clean slate.
*/
if (dvmReferenceTableEntries(&dvmThreadSelf()->internalLocalRefTable) != 0)
{
LOGW("Warning: tracked references remain post-initialization\n");
dvmDumpReferenceTable(&dvmThreadSelf()->internalLocalRefTable, "MAIN");
}
/* general debugging setup */
if (!dvmDebuggerStartup())
goto fail;
这段代码继续执行其它函数来执行其它的初始化和检查工作,如下所示:
1. dvmValidateBoxClasses
这个函数定义在文件dalvik/vm/reflect/Reflect.c中,用来验证Dalvik虚拟机中存在相应的装箱类,并且这些装箱类有且仅有一个成员变量,这个成员变量是用来描述对应的数字值的。这些装箱类包括java.lang.Boolean、java.lang.Character、java.lang.Float、java.lang.Double、java.lang.Byte、java.lang.Short、java.lang.Integer和java.lang.Long。
所谓装箱,就是可以自动将一个数值转换一个对象,例如,将数字1自动转换为一个java.lang.Integer对象。相应地,也要求能将一个装箱类对象转换成一个数字,例如,将一个值等于1的java.lang.Integer对象转换为数字1。
2. dvmPrepMainForJni
这个函数定义在文件dalvik/vm/Thread.c中,用来准备主线程的JNI环境,即将在前面的Step 5中为主线程创建的Thread对象与在前面Step 4中创建的JNI环境关联起来。回忆在前面的Step 4中,虽然我们已经为当前线程创建好一个JNI环境了,但是还没有将该JNI环境与主线程关联,也就是还没有将主线程的ID设置到该JNI环境中去。
3. registerSystemNatives
这个函数定义在文件dalvik/vm/Init.c中,它调用另外一个函数jniRegisterSystemMethods,后者接着又调用了函数registerCoreLibrariesJni来为Java核心类注册JNI方法。函数registerCoreLibrariesJni定义在文件libcore/luni/src/main/native/Register.cpp中。
4. dvmCreateStockExceptions
这个函数定义在文件dalvik/vm/alloc/Alloc.c中,用来预创建一些与内存分配有关的异常对象,并且将它们缓存起来,以便以后可以快速使用。这些异常对象包括java.lang.OutOfMemoryError、java.lang.InternalError和java.lang.NoClassDefFoundError。
5. dvmPrepMainThread
这个函数定义在文件dalvik/vm/Thread.c中,用来为主线程创建一个java.lang.ThreadGroup对象、一个java.lang.Thread对角和java.lang.VMThread对象。这些Java对象和在前面Step 5中创建的C++层Thread对象关联一起,共同用来描述Dalvik虚拟机的主线程。
6. dvmReferenceTableEntries
这个函数定义在文件dalvik/vm/ReferenceTable.h中,用来确保主线程当前不引用有任何Java对象,这是为了保证主线程接下来以干净的方式来执行程序入口。
7. dvmDebuggerStartup
这个函数定义在文件dalvik/vm/Debugger.c中,用来初始化Dalvik虚拟机的调试环境。注意,Dalvik虚拟机与Java虚拟机一样,都是通过JDWP协议来支持远程调试的。
上述初始化和检查操作执行完成之后,我们再来看最后一段代码:
[cpp]view plaincopyprint?
1. /*
2. * Init for either zygote mode or non-zygote mode. The key difference
3. * is that we don't start any additional threads in Zygote mode.
4. */
5. if (gDvm.zygote) {
6. if (!dvmInitZygote())
7. goto fail;
8. } else {
9. if (!dvmInitAfterZygote())
10. goto fail;
11. }
12.
13. ......
14.
15. return 0;
16.
17. fail:
18. dvmShutdown();
19. return 1;
20. }
/*
* Init for either zygote mode or non-zygote mode. The key difference
* is that we don't start any additional threads in Zygote mode.
*/
if (gDvm.zygote) {
if (!dvmInitZygote())
goto fail;
} else {
if (!dvmInitAfterZygote())
goto fail;
}
......
return 0;
fail:
dvmShutdown();
return 1;
}
这段代码完成Dalvik虚拟机的最后一步初始化工作。它检查Dalvik虚拟机是否指定了-Xzygote启动选项。如果指定了的话,那么就说明当前是在Zyogte进程中启动Dalvik虚拟机,因此,接下来就会调用函数dvmInitZygote来执行最后一步初始化工作。否则的话,就会调用另外一个函数dvmInitAfterZygote来执行最后一步初始化工作。
由于当前是在Zyogte进程中启动Dalvik虚拟机的,因此,接下来我们就继续分析函数dvmInitZygote的实现。在接下来的文章中分析Android应用程序进程的创建过程时,我们再分析函数dvmInitAfterZygote的实现。
Step 6. dvmInitZygote
[cpp]view plaincopyprint?
1. /*
2. * Do zygote-mode-only initialization.
3. */
4. static bool dvmInitZygote(void)
5. {
6. /* zygote goes into its own process group */
7. setpgid(0,0);
8.
9. return true;
10. }
/*
* Do zygote-mode-only initialization.
*/
static bool dvmInitZygote(void)
{
/* zygote goes into its own process group */
setpgid(0,0);
return true;
}
这个函数定义在文件dalvik/vm/Init.c中。
函数dvmInitZygote的实现很简单,它只是调用了系统调用setpgid来设置当前进程,即Zyogte进程的进程组ID。注意,在调用setpgid的时候,传递进去的两个参数均为0,这意味着Zyogte进程的进程组ID与进程ID是相同的,也就是说,Zyogte进程运行在一个单独的进程组里面。
这一步执行完成之后,Dalvik虚拟机的创建和初始化工作就完成了,回到前面的Step 1中,即AndroidRuntime类的成员函数start中,接下来就会调用AndroidRuntime类的另外一个成员函数startReg来注册Android核心类的JNI方法。
Step 7. AndroidRuntime.startReg
[cpp]view plaincopyprint?
1. /*
2. * Register android native functions with the VM.
3. */
4. /*static*/ int AndroidRuntime::startReg(JNIEnv* env)
5. {
6. /*
7. * This hook causes all future threads created in this process to be
8. * attached to the JavaVM. (This needs to go away in favor of JNI
9. * Attach calls.)
10. */
11. androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
12.
13. LOGV("--- registering native functions ---\n");
14.
15. /*
16. * Every "register" function calls one or more things that return
17. * a local reference (e.g. FindClass). Because we haven't really
18. * started the VM yet, they're all getting stored in the base frame
19. * and never released. Use Push/Pop to manage the storage.
20. */
21. env->PushLocalFrame(200);
22.
23. if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
24. env->PopLocalFrame(NULL);
25. return -1;
26. }
27. env->PopLocalFrame(NULL);
28.
29. //createJavaThread("fubar", quickTest, (void*) "hello");
30.
31. return 0;
32. }
/*
* Register android native functions with the VM.
*/
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
/*
* This hook causes all future threads created in this process to be
* attached to the JavaVM. (This needs to go away in favor of JNI
* Attach calls.)
*/
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
LOGV("--- registering native functions ---\n");
/*
* Every "register" function calls one or more things that return
* a local reference (e.g. FindClass). Because we haven't really
* started the VM yet, they're all getting stored in the base frame
* and never released. Use Push/Pop to manage the storage.
*/
env->PushLocalFrame(200);
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return -1;
}
env->PopLocalFrame(NULL);
//createJavaThread("fubar", quickTest, (void*) "hello");
return 0;
}
这个函数定义在文件frameworks/base/core/jni/AndroidRuntime.cpp中。
AndroidRuntime类的成员函数startReg首先调用函数androidSetCreateThreadFunc来设置一个线程创建钩子javaCreateThreadEtc。这个线程创建钩子是用来初始化一个Native线程的JNI环境的,也就是说,当我们在C++代码中创建一个Native线程的时候,函数javaCreateThreadEtc会被调用来初始化该Native线程的JNI环境。后面在分析Dalvik虚拟机线程的创建过程时,我们再详细分析函数javaCreateThreadEtc的实现。
AndroidRuntime类的成员函数startReg接着调用函数register_jni_procs来注册Android核心类的JNI方法。在注册JNI方法的过程中,需要在Native代码中引用到一些Java对象,这些Java对象引用需要记录在当前线程的一个Native堆栈中。但是此时Dalvik虚拟机还没有真正运行起来,也就是当前线程的Native堆栈还没有准备就绪。在这种情况下,就需要在注册JNI方法之前,手动地将在当前线程的Native堆栈中压入一个帧(Frame),并且在注册JNI方法之后,手动地将该帧弹出来。
当前线程的JNI环境是由参数env所指向的一个JNIEnv对象来描述的,通过调用它的成员函数PushLocalFrame和PopLocalFrame就可以手动地往当前线程的Native堆栈压入和弹出一个帧。注意,这个帧是一个本地帧,只可以用来保存Java对象在Native代码中的本地引用。
函数register_jni_procs的实现如下所示:
[cpp]view plaincopyprint?
1. static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
2. {
3. for (size_t i = 0; i < count; i++) {
4. if (array[i].mProc(env) < 0) {
5. return -1;
6. }
7. }
8. return 0;
9. }
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
for (size_t i = 0; i < count; i++) {
if (array[i].mProc(env) < 0) {
return -1;
}
}
return 0;
}
这个函数定义在文件frameworks/base/core/jni/AndroidRuntime.cpp中。
从前面的调用过程可以知道,参数array指向的是全局变量gRegJNI所描述的一个JNI方法注册函数表,其中,每一个表项都用一个RegJNIRec对象来描述,而每一个RegJNIRec对象都有一个成员变量mProc,指向一个JNI方法注册函数。通过依次调用这些注册函数,就可以将Android核心类的JNI方法注册到前面的所创建的Dalvik虚拟机中去。
通过观察全局变量gRegJNI所描述的JNI方法注册函数表,我们就可以看出注册了哪些Android核心类的JNI方法,如下所示:
[cpp]view plaincopyprint?
1. static const RegJNIRec gRegJNI[] = {
2. REG_JNI(register_android_debug_JNITest),
3. REG_JNI(register_com_android_internal_os_RuntimeInit),
4. REG_JNI(register_android_os_SystemClock),
5. REG_JNI(register_android_util_EventLog),
6. REG_JNI(register_android_util_Log),
7. REG_JNI(register_android_util_FloatMath),
8. REG_JNI(register_android_text_format_Time),
9. REG_JNI(register_android_pim_EventRecurrence),
10. REG_JNI(register_android_content_AssetManager),
11. REG_JNI(register_android_content_StringBlock),
12. REG_JNI(register_android_content_XmlBlock),
13. REG_JNI(register_android_emoji_EmojiFactory),
14. REG_JNI(register_android_security_Md5MessageDigest),
15. REG_JNI(register_android_text_AndroidCharacter),
16. REG_JNI(register_android_text_AndroidBidi),
17. REG_JNI(register_android_text_KeyCharacterMap),
18. REG_JNI(register_android_os_Process),
19. REG_JNI(register_android_os_Binder),
20. REG_JNI(register_android_view_Display),
21. REG_JNI(register_android_nio_utils),
22. REG_JNI(register_android_graphics_PixelFormat),
23. REG_JNI(register_android_graphics_Graphics),
24. REG_JNI(register_android_view_Surface),
25. REG_JNI(register_android_view_ViewRoot),
26. REG_JNI(register_com_google_android_gles_jni_EGLImpl),
27. REG_JNI(register_com_google_android_gles_jni_GLImpl),
28. REG_JNI(register_android_opengl_jni_GLES10),
29. REG_JNI(register_android_opengl_jni_GLES10Ext),
30. REG_JNI(register_android_opengl_jni_GLES11),
31. REG_JNI(register_android_opengl_jni_GLES11Ext),
32. REG_JNI(register_android_opengl_jni_GLES20),
33.
34. REG_JNI(register_android_graphics_Bitmap),
35. REG_JNI(register_android_graphics_BitmapFactory),
36. REG_JNI(register_android_graphics_BitmapRegionDecoder),
37. REG_JNI(register_android_graphics_Camera),
38. REG_JNI(register_android_graphics_Canvas),
39. REG_JNI(register_android_graphics_ColorFilter),
40. REG_JNI(register_android_graphics_DrawFilter),
41. REG_JNI(register_android_graphics_Interpolator),
42. REG_JNI(register_android_graphics_LayerRasterizer),
43. REG_JNI(register_android_graphics_MaskFilter),
44. REG_JNI(register_android_graphics_Matrix),
45. REG_JNI(register_android_graphics_Movie),
46. REG_JNI(register_android_graphics_NinePatch),
47. REG_JNI(register_android_graphics_Paint),
48. REG_JNI(register_android_graphics_Path),
49. REG_JNI(register_android_graphics_PathMeasure),
50. REG_JNI(register_android_graphics_PathEffect),
51. REG_JNI(register_android_graphics_Picture),
52. REG_JNI(register_android_graphics_PorterDuff),
53. REG_JNI(register_android_graphics_Rasterizer),
54. REG_JNI(register_android_graphics_Region),
55. REG_JNI(register_android_graphics_Shader),
56. REG_JNI(register_android_graphics_Typeface),
57. REG_JNI(register_android_graphics_Xfermode),
58. REG_JNI(register_android_graphics_YuvImage),
59. REG_JNI(register_com_android_internal_graphics_NativeUtils),
60.
61. REG_JNI(register_android_database_CursorWindow),
62. REG_JNI(register_android_database_SQLiteCompiledSql),
63. REG_JNI(register_android_database_SQLiteDatabase),
64. REG_JNI(register_android_database_SQLiteDebug),
65. REG_JNI(register_android_database_SQLiteProgram),
66. REG_JNI(register_android_database_SQLiteQuery),
67. REG_JNI(register_android_database_SQLiteStatement),
68. REG_JNI(register_android_os_Debug),
69. REG_JNI(register_android_os_FileObserver),
70. REG_JNI(register_android_os_FileUtils),
71. REG_JNI(register_android_os_MessageQueue),
72. REG_JNI(register_android_os_ParcelFileDescriptor),
73. REG_JNI(register_android_os_Power),
74. REG_JNI(register_android_os_StatFs),
75. REG_JNI(register_android_os_SystemProperties),
76. REG_JNI(register_android_os_UEventObserver),
77. REG_JNI(register_android_net_LocalSocketImpl),
78. REG_JNI(register_android_net_NetworkUtils),
79. REG_JNI(register_android_net_TrafficStats),
80. REG_JNI(register_android_net_wifi_WifiManager),
81. REG_JNI(register_android_nfc_NdefMessage),
82. REG_JNI(register_android_nfc_NdefRecord),
83. REG_JNI(register_android_os_MemoryFile),
84. REG_JNI(register_com_android_internal_os_ZygoteInit),
85. REG_JNI(register_android_hardware_Camera),
86. REG_JNI(register_android_hardware_SensorManager),
87. REG_JNI(register_android_media_AudioRecord),
88. REG_JNI(register_android_media_AudioSystem),
89. REG_JNI(register_android_media_AudioTrack),
90. REG_JNI(register_android_media_JetPlayer),
91. REG_JNI(register_android_media_ToneGenerator),
92.
93. REG_JNI(register_android_opengl_classes),
94. REG_JNI(register_android_bluetooth_HeadsetBase),
95. REG_JNI(register_android_bluetooth_BluetoothAudioGateway),
96. REG_JNI(register_android_bluetooth_BluetoothSocket),
97. REG_JNI(register_android_bluetooth_ScoSocket),
98. REG_JNI(register_android_server_BluetoothService),
99. REG_JNI(register_android_server_BluetoothEventLoop),
100. REG_JNI(register_android_server_BluetoothA2dpService),
101. REG_JNI(register_android_server_Watchdog),
102. REG_JNI(register_android_message_digest_sha1),
103. REG_JNI(register_android_ddm_DdmHandleNativeHeap),
104. REG_JNI(register_android_backup_BackupDataInput),
105. REG_JNI(register_android_backup_BackupDataOutput),
106. REG_JNI(register_android_backup_FileBackupHelperBase),
107. REG_JNI(register_android_backup_BackupHelperDispatcher),
108.
109. REG_JNI(register_android_app_NativeActivity),
110. REG_JNI(register_android_view_InputChannel),
111. REG_JNI(register_android_view_InputQueue),
112. REG_JNI(register_android_view_KeyEvent),
113. REG_JNI(register_android_view_MotionEvent),
114.
115. REG_JNI(register_android_content_res_ObbScanner),
116. REG_JNI(register_android_content_res_Configuration),
117. };
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_debug_JNITest),
REG_JNI(register_com_android_internal_os_RuntimeInit),
REG_JNI(register_android_os_SystemClock),
REG_JNI(register_android_util_EventLog),
REG_JNI(register_android_util_Log),
REG_JNI(register_android_util_FloatMath),
REG_JNI(register_android_text_format_Time),
REG_JNI(register_android_pim_EventRecurrence),
REG_JNI(register_android_content_AssetManager),
REG_JNI(register_android_content_StringBlock),
REG_JNI(register_android_content_XmlBlock),
REG_JNI(register_android_emoji_EmojiFactory),
REG_JNI(register_android_security_Md5MessageDigest),
REG_JNI(register_android_text_AndroidCharacter),
REG_JNI(register_android_text_AndroidBidi),
REG_JNI(register_android_text_KeyCharacterMap),
REG_JNI(register_android_os_Process),
REG_JNI(register_android_os_Binder),
REG_JNI(register_android_view_Display),
REG_JNI(register_android_nio_utils),
REG_JNI(register_android_graphics_PixelFormat),
REG_JNI(register_android_graphics_Graphics),
REG_JNI(register_android_view_Surface),
REG_JNI(register_android_view_ViewRoot),
REG_JNI(register_com_google_android_gles_jni_EGLImpl),
REG_JNI(register_com_google_android_gles_jni_GLImpl),
REG_JNI(register_android_opengl_jni_GLES10),
REG_JNI(register_android_opengl_jni_GLES10Ext),
REG_JNI(register_android_opengl_jni_GLES11),
REG_JNI(register_android_opengl_jni_GLES11Ext),
REG_JNI(register_android_opengl_jni_GLES20),
REG_JNI(register_android_graphics_Bitmap),
REG_JNI(register_android_graphics_BitmapFactory),
REG_JNI(register_android_graphics_BitmapRegionDecoder),
REG_JNI(register_android_graphics_Camera),
REG_JNI(register_android_graphics_Canvas),
REG_JNI(register_android_graphics_ColorFilter),
REG_JNI(register_android_graphics_DrawFilter),
REG_JNI(register_android_graphics_Interpolator),
REG_JNI(register_android_graphics_LayerRasterizer),
REG_JNI(register_android_graphics_MaskFilter),
REG_JNI(register_android_graphics_Matrix),
REG_JNI(register_android_graphics_Movie),
REG_JNI(register_android_graphics_NinePatch),
REG_JNI(register_android_graphics_Paint),
REG_JNI(register_android_graphics_Path),
REG_JNI(register_android_graphics_PathMeasure),
REG_JNI(register_android_graphics_PathEffect),
REG_JNI(register_android_graphics_Picture),
REG_JNI(register_android_graphics_PorterDuff),
REG_JNI(register_android_graphics_Rasterizer),
REG_JNI(register_android_graphics_Region),
REG_JNI(register_android_graphics_Shader),
REG_JNI(register_android_graphics_Typeface),
REG_JNI(register_android_graphics_Xfermode),
REG_JNI(register_android_graphics_YuvImage),
REG_JNI(register_com_android_internal_graphics_NativeUtils),
REG_JNI(register_android_database_CursorWindow),
REG_JNI(register_android_database_SQLiteCompiledSql),
REG_JNI(register_android_database_SQLiteDatabase),
REG_JNI(register_android_database_SQLiteDebug),
REG_JNI(register_android_database_SQLiteProgram),
REG_JNI(register_android_database_SQLiteQuery),
REG_JNI(register_android_database_SQLiteStatement),
REG_JNI(register_android_os_Debug),
REG_JNI(register_android_os_FileObserver),
REG_JNI(register_android_os_FileUtils),
REG_JNI(register_android_os_MessageQueue),
REG_JNI(register_android_os_ParcelFileDescriptor),
REG_JNI(register_android_os_Power),
REG_JNI(register_android_os_StatFs),
REG_JNI(register_android_os_SystemProperties),
REG_JNI(register_android_os_UEventObserver),
REG_JNI(register_android_net_LocalSocketImpl),
REG_JNI(register_android_net_NetworkUtils),
REG_JNI(register_android_net_TrafficStats),
REG_JNI(register_android_net_wifi_WifiManager),
REG_JNI(register_android_nfc_NdefMessage),
REG_JNI(register_android_nfc_NdefRecord),
REG_JNI(register_android_os_MemoryFile),
REG_JNI(register_com_android_internal_os_ZygoteInit),
REG_JNI(register_android_hardware_Camera),
REG_JNI(register_android_hardware_SensorManager),
REG_JNI(register_android_media_AudioRecord),
REG_JNI(register_android_media_AudioSystem),
REG_JNI(register_android_media_AudioTrack),
REG_JNI(register_android_media_JetPlayer),
REG_JNI(register_android_media_ToneGenerator),
REG_JNI(register_android_opengl_classes),
REG_JNI(register_android_bluetooth_HeadsetBase),
REG_JNI(register_android_bluetooth_BluetoothAudioGateway),
REG_JNI(register_android_bluetooth_BluetoothSocket),
REG_JNI(register_android_bluetooth_ScoSocket),
REG_JNI(register_android_server_BluetoothService),
REG_JNI(register_android_server_BluetoothEventLoop),
REG_JNI(register_android_server_BluetoothA2dpService),
REG_JNI(register_android_server_Watchdog),
REG_JNI(register_android_message_digest_sha1),
REG_JNI(register_android_ddm_DdmHandleNativeHeap),
REG_JNI(register_android_backup_BackupDataInput),
REG_JNI(register_android_backup_BackupDataOutput),
REG_JNI(register_android_backup_FileBackupHelperBase),
REG_JNI(register_android_backup_BackupHelperDispatcher),
REG_JNI(register_android_app_NativeActivity),
REG_JNI(register_android_view_InputChannel),
REG_JNI(register_android_view_InputQueue),
REG_JNI(register_android_view_KeyEvent),
REG_JNI(register_android_view_MotionEvent),
REG_JNI(register_android_content_res_ObbScanner),
REG_JNI(register_android_content_res_Configuration),
};
上述函数表同样是定义在文件frameworks/base/core/jni/AndroidRuntime.cpp中。
回到AndroidRuntime类的成员函数startReg中,接下来我们就继续分析函数androidSetCreateThreadFunc的实现,以便可以了解线程创建钩子javaCreateThreadEtc的注册过程。
Step 8. androidSetCreateThreadFunc
[cpp]view plaincopyprint?
1. static android_create_thread_fn gCreateThreadFn = androidCreateRawThreadEtc;
2.
3. ......
4.
5. void androidSetCreateThreadFunc(android_create_thread_fn func)
6. {
7. gCreateThreadFn = func;
8. }
static android_create_thread_fn gCreateThreadFn = androidCreateRawThreadEtc;
......
void androidSetCreateThreadFunc(android_create_thread_fn func)
{
gCreateThreadFn = func;
}
这个函数定义在文件frameworks/base/libs/utils/Threads.cpp中。
从这里就可以看到,线程创建钩子javaCreateThreadEtc被保存在一个函数指针gCreateThreadFn中。注意,函数指针gCreateThreadFn默认是指向函数androidCreateRawThreadEtc的,也就是说,如果我们不设置线程创建钩子的话,函数androidCreateRawThreadEtc就是默认使用的线程创建函数。后面在分析Dalvik虚拟机线程的创建过程时,我们再详细分析函数指针gCreateThreadFn是如何使用的。
至此,我们就分析完成Dalvik虚拟机在Zygote进程中的启动过程,这个启动过程主要就是完成了以下四个事情:
1.创建了一个Dalvik虚拟机实例;
2.加载了Java核心类及其JNI方法;
3.为主线程的设置了一个JNI环境;
4.注册了Android核心类的JNI方法。
换句话说,就是Zygote进程为Android系统准备好了一个Dalvik虚拟机实例,以后Zygote进程在创建Android应用程序进程的时候,就可以将它自身的Dalvik虚拟机实例复制到新创建Android应用程序进程中去,从而加快了Android应用程序进程的启动过程。此外,Java核心类和Android核心类(位于dex文件中),以及它们的JNI方法(位于so文件中),都是以内存映射的方式来读取的,因此,Zygote进程在创建Android应用程序进程的时候,除了可以将自身的Dalvik虚拟机实例复制到新创建的Android应用程序进程之外,还可以与新创建的Android应用程序进程共享Java核心类和Android核心类,以及它们的JNI方法,这样就可以节省内存消耗。
同时,我们也应该看到,Zygote进程为了加快Android应用程序进程的启动过程,牺牲了自己的启动速度,因为它需要加载大量的Java核心类,以及注册大量的Android核心类JNI方法。Dalvik虚拟机在加载Java核心类的时候,还需要对它们进行验证以及优化,这些通常都是比较耗时的。又由于Zygote进程是由init进程启动的,也就是说Zygote进程在是开机的时候进行启动的,因此,Zygote进程的牺牲是比较大的。不过毕竟我们在玩手机的时候,很少会关机,也就是很少开机,因此,牺牲Zygote进程的启动速度是值得的,换来的是Android应用程序的快速启动。而且,Android系统为了加快Java类的加载速度,还会想方设法地提前对Dex文件进行验证和优化,这些措施具体参考Dalvik Optimization and Verification With dexopt一文。
学习了Dalvik虚拟机的启动过程之后,在接下来的一篇文章中,我们就继续分析Dalvik虚拟机的运行机制,敬请关注!
Dalvik虚拟机的运行过程分析
分类:Android 2013-05-20 00:57 1753人阅读评论(8)收藏 举报
AndroidDalvikRun运行
在前面一篇文章中,我们分析了Dalvik虚拟机在Zygote进程中的启动过程。Dalvik虚拟机启动完成之后,也就是在各个子模块初始化完成以及加载了相应的Java核心类库之后,就是可以执行Java代码了。当然,Dalvik虚拟机除了可以执行Java代码之外,还可以执行Native代码,也就是C和C++代码。在本文中,我们就将继续以Zygote进程的启动过程为例,来分析Dalvik虚拟机的运行过程。
从前面Dalvik虚拟机的启动过程分析一文可以知道,Dalvik虚拟机在Zygote进程中启动完成之后,就会获得一个JavaVM实例和一个JNIEnv实例。其中,获得的JavaVM实例就是用来描述Zygote进程的Dalvik虚拟机实例,而获得的JNIEnv实例描述的是Zygote进程的主线程的JNI环境。紧接着,Zygote进程就会通过前面获得的JNIEnv实例的成员函数CallStaticVoidMethod来调用com.android.internal.os.ZygoteInit类的静态成员函数main。这就相当于是将com.android.internal.os.ZygoteInit类的静态成员函数main作为Java代码的入口点。
接下来,我们就从JNIEnv类的成员函数CallStaticVoidMethod开始,分析Dalvik虚拟机的运行过程,如图1所示:
图1 Dalvik虚拟机的运行过程
这个过程可以分为9个步骤,接下来我们就详细分析每一个步骤。
Step 1. JNIEnv.CallStaticVoidMethod
[cpp]view plaincopyprint?
1. struct _JNIEnv;
2. ......
3. typedef _JNIEnv JNIEnv;
4. ......
5.
6. struct _JNIEnv {
7. /* do not rename this; it does not seem to be entirely opaque */
8. const struct JNINativeInterface* functions;
9. ......
10.
11. void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)
12. {
13. va_list args;
14. va_start(args, methodID);
15. functions->CallStaticVoidMethodV(this, clazz, methodID, args);
16. va_end(args);
17. }
18.
19. ......
20. };
struct _JNIEnv;
......
typedef _JNIEnv JNIEnv;
......
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
......
void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...)
{
va_list args;
va_start(args, methodID);
functions->CallStaticVoidMethodV(this, clazz, methodID, args);
va_end(args);
}
......
};
这个函数定义在文件dalvik/libnativehelper/include/nativehelper/jni.h中。
JNIEnv实际上是一个结构,它有一个成员变量functions,指向的是一个回调函数表。这个回调函数表使用一个JNINativeInterface对象来描述。JNIEnv结构体的成员函数CallStaticVoidMethod的实现很简单,它只是调用该回调函数表中的CallStaticVoidMethodV函数来执行参数clazz和methodID所描述的Java代码。
Step 2. JNINativeInterface.CallStaticVoidMethodV
[cpp]view plaincopyprint?
1. struct JNINativeInterface {
2. ......
3.
4. void (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list);
5.
6. ......
7. };
struct JNINativeInterface {
......
void (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list);
......
};
这个函数定义在文件dalvik/libnativehelper/include/nativehelper/jni.h中。
JNINativeInterface是一个结构体,它的成员变量CallStaticVoidMethodV是一个函数指针。
从前面Dalvik虚拟机的启动过程分析一文可以知道,Dalvik虚拟机在内部为Zygote进程的主线程所创建的Java环境是用一个JNIEnvExt结构体来描述的,并且这个JNIEnvExt结构体会被强制转换成一个JNIEnv结构体返回给Zygote进程。
JNIEnvExt结构体定义在文件dalvik/vm/JniInternal.h中,如下所示:
[cpp]view plaincopyprint?
1. typedef struct JNIEnvExt {
2. const struct JNINativeInterface* funcTable; /* must be first */
3.
4. ......
5. } JNIEnvExt;
typedef struct JNIEnvExt {
const struct JNINativeInterface* funcTable; /* must be first */
......
} JNIEnvExt;
从这里就可以看出,虽然结构体JNIEnvExt和JNIEnv之间没有继承关系,但是它们的第一个成员变量的类型是一致的,也就是它们都是指向一个类型为JNINativeInterface的回调函数表,因此,Dalvik虚拟机可以将一个JNIEnvExt结构体强制转换成一个JNIEnv结构体返回给Zygote进程,这时候我们通过JNIEnv结构体来访问其成员变量functions所描述的回调函数表时,实际访问到的是对应的JNIEnvExt结构体的成员变量funcTable所描述的回调函数表。
为什么不直接让JNIEnvExt结构体从JNIEnv结构体继承下来呢?这样把一个JNIEnvExt结构体转换为一个JNIEnv结构体就是相当直观的。然而,Dalvik虚拟机的源代码并一定是要以C++语言的形式来编译的,它也可以以C语言的形式来编译的。由于C语言没有继承的概念,因此,为了使得Dalvik虚拟机的源代码能同时兼容C++和C,这里就使用了一个Trick:只要两个结构体的内存布局相同,它们就可以相互转换访问。当然,这并不要求两个结构体的内存布局完全相同,但是至少开始部分要求是相同的。在这种情况下,将一个结构体强制转换成另外一个结构体之外,只要不去访问内存布局不一致的地方,就没有问题。在Android系统的Native代码中,我们可以常常看到这种Trick。
接下来,我们需要搞清楚的是JNIEnvExt结构体的成员变量funcTable指向的回调函数表是什么。同样是从前面Dalvik虚拟机的启动过程分析一文可以知道,Dalvik虚拟机在创建一个JNIEnvExt结构体的时候,会将它的成员变量funcTable指向全局变量gNativeInterface所描述的一个回调函数表。
gNativeInterface定义在文件dalvik/vm/Jni.c中,如下所示:
[cpp]view plaincopyprint?
1. static const struct JNINativeInterface gNativeInterface = {
2. ......
3.
4. CallStaticVoidMethodV,
5.
6. ......
7. };
static const struct JNINativeInterface gNativeInterface = {
......
CallStaticVoidMethodV,
......
};
在这个回调函数表中,名称为CallStaticVoidMethodV的函数指针指向的是一个同名函数CallStaticVoidMethodV。
函数CallStaticVoidMethodV同样是定义在文件dalvik/vm/Jni.c中,不过它是通过宏CALL_STATIC来定义的,如下所示:
[cpp]view plaincopyprint?
1. #define CALL_STATIC(_ctype, _jname, _retfail, _retok, _isref) \
2. ...... \
3. static _ctype CallStatic##_jname##MethodV(JNIEnv* env, jclass jclazz, \
4. jmethodID methodID, va_list args) \
5. { \
6. UNUSED_PARAMETER(jclazz); \
7. JNI_ENTER(); \
8. JValue result; \
9. dvmCallMethodV(_self, (Method*)methodID, NULL, true, &result, args);\
10. if (_isref && !dvmCheckException(_self)) \
11. result.l = addLocalReference(env, result.l); \
12. JNI_EXIT(); \
13. return _retok; \
14. }
15. ...... \
16. CALL_STATIC(void, Void, , , false);
#define CALL_STATIC(_ctype, _jname, _retfail, _retok, _isref) \
...... \
static _ctype CallStatic##_jname##MethodV(JNIEnv* env, jclass jclazz, \
jmethodID methodID, va_list args) \
{ \
UNUSED_PARAMETER(jclazz); \
JNI_ENTER(); \
JValue result; \
dvmCallMethodV(_self, (Method*)methodID, NULL, true, &result, args);\
if (_isref && !dvmCheckException(_self)) \
result.l = addLocalReference(env, result.l); \
JNI_EXIT(); \
return _retok; \
}
...... \
CALL_STATIC(void, Void, , , false);
通过上面的分析就可以知道,在JNIEnvExt结构体的成员变量funcTable所描述的回调函数表中,名称为CallStaticVoidMethodV的函数指针指向的是一个同名函数CallStaticVoidMethodV。这就是说,我们通过JNIEnv结构体的成员变量functions所访问到的名称为CallStaticVoidMethodV函数指针实际指向的是函数CallStaticVoidMethodV。
Step 3. CallStaticVoidMethodV
我们将上面的CALL_STATIC宏开之后,就可以得到函数CallStaticVoidMethodV的实现,如下所示:
[cpp]view plaincopyprint?
1. static _ctype CallStaticVoidMethodV(JNIEnv* env, jclass jclazz,
2. jmethodID methodID, va_list args)
3. {
4. UNUSED_PARAMETER(jclazz);
5. JNI_ENTER();
6. JValue result;
7. dvmCallMethodV(_self, (Method*)methodID, NULL, true, &result, args);
8. if (_isref && !dvmCheckException(_self))
9. result.l = addLocalReference(env, result.l);
10. JNI_EXIT();
11. return _retok;
12. }
static _ctype CallStaticVoidMethodV(JNIEnv* env, jclass jclazz,
jmethodID methodID, va_list args)
{
UNUSED_PARAMETER(jclazz);
JNI_ENTER();
JValue result;
dvmCallMethodV(_self, (Method*)methodID, NULL, true, &result, args);
if (_isref && !dvmCheckException(_self))
result.l = addLocalReference(env, result.l);
JNI_EXIT();
return _retok;
}
函数CallStaticVoidMethodV的实现很简单,它通过调用另外一个函数dvmCallMethodV来执行由参数jclazz和methodID所描述的Java代码,因此,接下来我们就继续分析函数dvmCallMethodV的实现。
Step 4. dvmCallMethodV
[cpp]view plaincopyprint?
1. void dvmCallMethodV(Thread* self, const Method* method, Object* obj,
2. bool fromJni, JValue* pResult, va_list args)
3. {
4. ......
5.
6. if (dvmIsNativeMethod(method)) {
7. TRACE_METHOD_ENTER(self, method);
8. /*
9. * Because we leave no space for local variables, "curFrame" points
10. * directly at the method arguments.
11. */
12. (*method->nativeFunc)(self->curFrame, pResult, method, self);
13. TRACE_METHOD_EXIT(self, method);
14. } else {
15. dvmInterpret(self, method, pResult);
16. }
17.
18. ......
19. }
void dvmCallMethodV(Thread* self, const Method* method, Object* obj,
bool fromJni, JValue* pResult, va_list args)
{
......
if (dvmIsNativeMethod(method)) {
TRACE_METHOD_ENTER(self, method);
/*
* Because we leave no space for local variables, "curFrame" points
* directly at the method arguments.
*/
(*method->nativeFunc)(self->curFrame, pResult, method, self);
TRACE_METHOD_EXIT(self, method);
} else {
dvmInterpret(self, method, pResult);
}
......
}
这个函数定义在文件dalvik/vm/interp/Stack.c中。
函数dvmCallMethodV首先检查参数method描述的函数是否是一个JNI方法。如果是的话,那么它所指向的一个Method对象的成员变量nativeFunc就指向该JNI方法的地址,因此就可以直接对它进行调用。否则的话,就说明参数method描述的是一个Java函数,这时候就需要继续调用函数dvmInterpret来执行它的代码。
Step 5. dvmInterpret
[cpp]view plaincopyprint?
1. void dvmInterpret(Thread* self, const Method* method, JValue* pResult)
2. {
3. InterpState interpState;
4. ......
5.
6. /*
7. * Initialize working state.
8. *
9. * No need to initialize "retval".
10. */
11. interpState.method = method;
12. interpState.fp = (u4*) self->curFrame;
13. interpState.pc = method->insns;
14. ......
15.
16. typedef bool (*Interpreter)(Thread*, InterpState*);
17. Interpreter stdInterp;
18. if (gDvm.executionMode == kExecutionModeInterpFast)
19. stdInterp = dvmMterpStd;
20. #if defined(WITH_JIT)
21. else if (gDvm.executionMode == kExecutionModeJit)
22. /* If profiling overhead can be kept low enough, we can use a profiling
23. * mterp fast for both Jit and "fast" modes. If overhead is too high,
24. * create a specialized profiling interpreter.
25. */
26. stdInterp = dvmMterpStd;
27. #endif
28. else
29. stdInterp = dvmInterpretStd;
30.
31. change = true;
32. while (change) {
33. switch (interpState.nextMode) {
34. case INTERP_STD:
35. LOGVV("threadid=%d: interp STD\n", self->threadId);
36. change = (*stdInterp)(self, &interpState);
37. break;
38. case INTERP_DBG:
39. LOGVV("threadid=%d: interp DBG\n", self->threadId);
40. change = dvmInterpretDbg(self, &interpState);
41. break;
42. default:
43. dvmAbort();
44. }
45. }
46.
47. *pResult = interpState.retval;
48.
49. ......
50. }
void dvmInterpret(Thread* self, const Method* method, JValue* pResult)
{
InterpState interpState;
......
/*
* Initialize working state.
*
* No need to initialize "retval".
*/
interpState.method = method;
interpState.fp = (u4*) self->curFrame;
interpState.pc = method->insns;
......
typedef bool (*Interpreter)(Thread*, InterpState*);
Interpreter stdInterp;
if (gDvm.executionMode == kExecutionModeInterpFast)
stdInterp = dvmMterpStd;
#if defined(WITH_JIT)
else if (gDvm.executionMode == kExecutionModeJit)
/* If profiling overhead can be kept low enough, we can use a profiling
* mterp fast for both Jit and "fast" modes. If overhead is too high,
* create a specialized profiling interpreter.
*/
stdInterp = dvmMterpStd;
#endif
else
stdInterp = dvmInterpretStd;
change = true;
while (change) {
switch (interpState.nextMode) {
case INTERP_STD:
LOGVV("threadid=%d: interp STD\n", self->threadId);
change = (*stdInterp)(self, &interpState);
break;
case INTERP_DBG:
LOGVV("threadid=%d: interp DBG\n", self->threadId);
change = dvmInterpretDbg(self, &interpState);
break;
default:
dvmAbort();
}
}
*pResult = interpState.retval;
......
}
这个函数定义在文件dalvik/vm/interp/Interp.c中。
在前面Dalvik虚拟机的启动过程分析一文中提到,Dalvik虚拟机支持三种执行模式:portable、fast和jit,它们分别使用kExecutionModeInterpPortable、kExecutionModeInterpFast和kExecutionModeJit三个常量来表示。Dalvik虚拟机在启动的时候,会通过解析命令行参数获得所要执行的模式,并且记录在全局变量gDvm所指向的一个DvmGlobals结构体的成员变量executionMode中。
kExecutionModeInterpPortable表示Dalvik虚拟机以可移植模式来解释Java代码,也就是这种执行模式可以应用在任何一个平台上,例如,可以同时在arm和x86各个平台上执行。这时候使用函数dvmInterpretStd来作为Java代码的执行函数。
kExecutionModeInterpFast表示Dalvik虚拟机以快速模式来解释Java代码。以这种模式执行的Dalvik虚拟机是针对某一个特定的目标平台进行过优化的,因此,它可以更快速地对Java代码进行解释以及执行。这时候使用函数dvmMterpStd来作为Java代码的执行函数。
kExecutionModeJit表示Dalvik虚拟机支持JIT模式来执行Java代码,也就是先将Java代码动态编译成Native代码再执行。这时候使用函数dvmMterpStd来作为Java代码的执行函数。
我们可以将函数dvmInterpretStd和dvmMterpStd理解为Dalvik虚拟机的解释器入口点。很显然,解释器是虚拟机的核心模块,它的性能关乎到整个虚拟机的性能。Dalvik虚拟机的解释器开始的时候都是以C语言来实现的,后来为了提到性能,就改成以汇编语言来实现。注意,无论Dalvik虚拟机的解释器是以C语言来实现,还是以汇编语言来实现,它们的源代码都是以一种模块化方法来自动生成的,并且能够根据某一个特定的平台进行优化。
所谓模块化代码生成方法,就是说将解释器的实现划分成若干个模块,每一个模块都对应有一系列的输入文件(本身也是源代码文件),最后通过工具(一个Python脚本)将这些输入文件组装起来形成一个C语言文件或者汇编语言文件。这个最终得到的C语言文件或者汇编语言文件就是Dalvik虚拟机的解释器的实现文件。有了这种模块化代码生成方法之后,为某一个特定的平台生成优化过的解释器就是相当容易的:我们只需要为该平台的Dalvik虚拟机解释器的相关模块提供一个特殊版本的输入文件即可。也就是说,我们需要为每一个支持的平台提供一个配置文件,该配置文件描述了该平台的Dalvik虚拟机解释器的各个模块所要使用的输入文件。这种模块化代码生成方法不仅能避免手动编写解释器容易出错的问题,还能方便快速地将Dalvik虚拟机从一个平台移植到另外一个平台。
采取了模块化方法来生成Dalvik虚拟机解释器的源代码之后,Dalvik虚拟机解释器的源代码整体上就划分成两部分:第一部分相当于一个Wrapper,定义在dalvik/vm/interp目录中;第二部分对应于具体的实现,定义在dalvik/vm/mterp目录中。
在dalvik/vm/mterp目录中,又定义了一系列的输入文件,以及各个平台所使用的配置文件。有了这些输入文件和配置文件之后,就可以通过一个Python脚本来为每一个平台生成一个Dalvik虚拟机解释器的输出文件,并且保存在dalvik/vm/mterp/out目录中。由于针对各个平台生成的输出文件是一个汇编语言文件,即一个 *.S文件,为了方便理解它的逻辑,每一个汇编语言文件都对应有一个C语言文件。
虽然Dalvik虚拟机解释器针对每一个平台都有一个优化的版本,但是同时也会提供一个通用的版本,也就是一个可移植版本。该可移植版本的解释器源文件只有C语言实现版本,定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中。在本文中,我们只考虑可移植版本的Dalvik虚拟机解释器的实现,也就是只考虑执行模式为kExecutionModeInterpPortable的Dalvik虚拟机的运行机制。从前面的分析可以知道,该可移植版本的Dalvik虚拟机解释器的入口函数为dvmInterpretStd。
函数dvmInterpretStd在执行之前,需要知道解释器的当前状态,也就是它所要执行的Java函数及其指令入口,以及当前要执行的线程的堆栈。这些信息都用一个InterpState结构体来描述。其中,这个InterpState结构体的成员变量method描述的是要执行的Java函数,成员变量fp描述的是要执行的线程的当前堆栈帧,成员变量pc描述的是要执行的Java函数的入口点。
在函数dvmInterpret中,参数self描述的是当前用来执行Java代码的线程,而参数method描述的是要执行的Java函数。通过这两个参数我们就可以初始化上述的InterpState结构体。Dalvik虚拟机解释器除了可以在正常模式执行之外,还可以在调试模式执行,即决于上述初始化后得到的InterpState结构体的成员变量nextMode的值。
在本文中,我们只考虑正常模式执行的Dalvik虚拟机解释器的实现,也就是我们只分析函数dvmInterpretStd的实现。函数dvmInterpretStd解释完成指定的Java函数之后,获得的返回值就保存在上述InterpState结构体的成员变量retval中。
Step 6. dvmInterpretStd
[cpp]view plaincopyprint?
1. #define INTERP_FUNC_NAME dvmInterpretStd
2. ......
3.
4. bool INTERP_FUNC_NAME(Thread* self, InterpState* interpState)
5. {
6. ......
7.
8. DvmDex* methodClassDex; // curMethod->clazz->pDvmDex
9. JValue retval;
10.
11. /* core state */
12. const Method* curMethod; // method we're interpreting
13. const u2* pc; // program counter
14. u4* fp; // frame pointer
15. u2 inst; // current instruction
16. ......
17.
18. /* copy state in */
19. curMethod = interpState->method;
20. pc = interpState->pc;
21. fp = interpState->fp;
22. retval = interpState->retval; /* only need for kInterpEntryReturn? */
23.
24. methodClassDex = curMethod->clazz->pDvmDex;
25. ......
26.
27. while (1) {
28. ......
29.
30. /* fetch the next 16 bits from the instruction stream */
31. inst = FETCH(0);
32.
33. switch (INST_INST(inst)) {
34. ......
35.
36. HANDLE_OPCODE(OP_INVOKE_DIRECT /*vB, {vD, vE, vF, vG, vA}, meth@CCCC*/)
37. GOTO_invoke(invokeDirect, false);
38. OP_END
39.
40. ......
41.
42. HANDLE_OPCODE(OP_RETURN /*vAA*/)
43. vsrc1 = INST_AA(inst);
44. ......
45. retval.i = GET_REGISTER(vsrc1);
46. GOTO_returnFromMethod();
47. OP_END
48.
49. ......
50.
51. }
52. }
53.
54. ......
55.
56. /* export state changes */
57. interpState->method = curMethod;
58. interpState->pc = pc;
59. interpState->fp = fp;
60. /* debugTrackedRefStart doesn't change */
61. interpState->retval = retval; /* need for _entryPoint=ret */
62. interpState->nextMode =
63. (INTERP_TYPE == INTERP_STD) ? INTERP_DBG : INTERP_STD;
64. ......
65.
66. return true;
67. }
#define INTERP_FUNC_NAME dvmInterpretStd
......
bool INTERP_FUNC_NAME(Thread* self, InterpState* interpState)
{
......
DvmDex* methodClassDex; // curMethod->clazz->pDvmDex
JValue retval;
/* core state */
const Method* curMethod; // method we're interpreting
const u2* pc; // program counter
u4* fp; // frame pointer
u2 inst; // current instruction
......
/* copy state in */
curMethod = interpState->method;
pc = interpState->pc;
fp = interpState->fp;
retval = interpState->retval; /* only need for kInterpEntryReturn? */
methodClassDex = curMethod->clazz->pDvmDex;
......
while (1) {
......
/* fetch the next 16 bits from the instruction stream */
inst = FETCH(0);
switch (INST_INST(inst)) {
......
HANDLE_OPCODE(OP_INVOKE_DIRECT /*vB, {vD, vE, vF, vG, vA}, meth@CCCC*/)
GOTO_invoke(invokeDirect, false);
OP_END
......
HANDLE_OPCODE(OP_RETURN /*vAA*/)
vsrc1 = INST_AA(inst);
......
retval.i = GET_REGISTER(vsrc1);
GOTO_returnFromMethod();
OP_END
......
}
}
......
/* export state changes */
interpState->method = curMethod;
interpState->pc = pc;
interpState->fp = fp;
/* debugTrackedRefStart doesn't change */
interpState->retval = retval; /* need for _entryPoint=ret */
interpState->nextMode =
(INTERP_TYPE == INTERP_STD) ? INTERP_DBG : INTERP_STD;
......
return true;
}
这个函数定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中。
函数dvmInterpretStd对应的是Dalvik虚拟机解释器的可移植版本实现,它大概可以划分三个逻辑块:
1.初始化当前要解释的类(methodClassDex)及其成员变量函数(curMethod)、栈帧(fp)、程序计数器(pc)和返回值(retval),这些值都可以从参数interpState获得。
2.在一个无限while循环中,通过FETCH宏依次获得当前程序计数器(pc)中的指令inst,并且通过宏INST_INST获得指令inst的类型,最后就switch到对应的分支去解释指令inst。
宏FETCH和INST_INST的定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中,如下所示:
[cpp]view plaincopyprint?
1. /*
2. * Get 16 bits from the specified offset of the program counter. We always
3. * want to load 16 bits at a time from the instruction stream -- it's more
4. * efficient than 8 and won't have the alignment problems that 32 might.
5. *
6. * Assumes existence of "const u2* pc".
7. */
8. #define FETCH(_offset) (pc[(_offset)])
9.
10. /*
11. * Extract instruction byte from 16-bit fetch (_inst is a u2).
12. */
13. #define INST_INST(_inst) ((_inst) & 0xff)
/*
* Get 16 bits from the specified offset of the program counter. We always
* want to load 16 bits at a time from the instruction stream -- it's more
* efficient than 8 and won't have the alignment problems that 32 might.
*
* Assumes existence of "const u2* pc".
*/
#define FETCH(_offset) (pc[(_offset)])
/*
* Extract instruction byte from 16-bit fetch (_inst is a u2).
*/
#define INST_INST(_inst) ((_inst) & 0xff)
从这里我们就可以看出,pc实际上指向的就是当前要执行的Java函数的方法区,也就是一个指令流。这个指令流包含了很多指令,需要通过一个while循环来依次对它们进行解释,直到碰到一个return指令为止。这就是Dalvik虚拟机解释器的核心功能。例如,假设当前遇到的是一条OP_INVOKE_DIRECT指令,它表示要调用当前类的一个非静态非虚成员函数,这时候就会通过宏GOTO_invoke跳到invokeDirect这个分支去。
宏HANDLE_OPCODE和GOTO_invoke定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中,如下所示:
[cpp]view plaincopyprint?
1. # define HANDLE_OPCODE(_op) case _op:
2.
3. #define GOTO_invoke(_target, _methodCallRange) \
4. do { \
5. methodCallRange = _methodCallRange; \
6. goto _target; \
7. } while(false)
# define HANDLE_OPCODE(_op) case _op:
#define GOTO_invoke(_target, _methodCallRange) \
do { \
methodCallRange = _methodCallRange; \
goto _target; \
} while(false)
分支invokeDirect是通过另外一个宏GOTO_TARGET来定义的,在接下来的Step 7中我们再分析。
当遇到return指令时,例如,遇到OP_RETURN指令时,首先会通过宏INST_AA和GET_REGISTER来获得函数的返回值,接着再通过宏GOTO_returnFromMethod跳到returnFromMethod分支去结束整个while循环。
注意,当指令inst是return指令时,它的执行结果即为当要正在解释的Java函数返回值。通过宏INST_AA可以知道一条指令的执行结果保存在哪个寄存器中,而通过宏GET_REGISTER可以获得该寄存器的值。
宏INST_AA和GET_REGISTER定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中,如下所示:
[cpp]view plaincopyprint?
1. /*
2. * Get the 8-bit "vAA" 8-bit register index from the instruction word.
3. * (_inst is u2)
4. */
5. #define INST_AA(_inst) ((_inst) >> 8)
6.
7.
8. # define GET_REGISTER(_idx) \
9. ( (_idx) < curMethod->registersSize ? \
10. (fp[(_idx)]) : (assert(!"bad reg"),1969) )
/*
* Get the 8-bit "vAA" 8-bit register index from the instruction word.
* (_inst is u2)
*/
#define INST_AA(_inst) ((_inst) >> 8)
# define GET_REGISTER(_idx) \
( (_idx) < curMethod->registersSize ? \
(fp[(_idx)]) : (assert(!"bad reg"),1969) )
宏GOTO_returnFromMethod定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中,如下所示:
[cpp]view plaincopyprint?
1. #define GOTO_returnFromMethod() goto returnFromMethod;
#define GOTO_returnFromMethod() goto returnFromMethod;
分支returnFromMethod和invokeDirect一样,都是通过宏GOTO_TARGET来定义的,在接下来的Step 9中我们再分析。
接下来,我们就以分支invokeDirect为例来说明Dalvik虚拟机解释一条指令的过程,接着再以分支returnFromMethod的实现来说明Dalvik虚拟机解释器从一个函数返回的过程。
Step 7. invokeDirect
[cpp]view plaincopyprint?
1. GOTO_TARGET(invokeDirect, bool methodCallRange)
2. {
3. ......
4.
5. vsrc1 = INST_AA(inst); /* AA (count) or BA (count + arg 5) */
6. ref = FETCH(1); /* method ref */
7. vdst = FETCH(2); /* 4 regs -or- first reg */
8.
9. EXPORT_PC();
10. ......
11.
12. methodToCall = dvmDexGetResolvedMethod(methodClassDex, ref);
13. ......
14.
15. GOTO_invokeMethod(methodCallRange, methodToCall, vsrc1, vdst);
16. }
17. GOTO_TARGET_END
GOTO_TARGET(invokeDirect, bool methodCallRange)
{
......
vsrc1 = INST_AA(inst); /* AA (count) or BA (count + arg 5) */
ref = FETCH(1); /* method ref */
vdst = FETCH(2); /* 4 regs -or- first reg */
EXPORT_PC();
......
methodToCall = dvmDexGetResolvedMethod(methodClassDex, ref);
......
GOTO_invokeMethod(methodCallRange, methodToCall, vsrc1, vdst);
}
GOTO_TARGET_END
根据Step 6的分析,invokeDirect是一个分支,它通过宏GOTO_TARGET定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中。
分支invokeDirect用来调用当前类(methodClassDex)的非静态非虚成员函数。这个被调用的成员函数的引用可以通过宏FETCH(1)来获取。知道了被调用的成员函数的引用之后,就可以通过调用函数dvmDexGetResolvedMethod来获得对应的成员函数(methodToCall)。
此外,宏FETCH(2)用来获得要传递给成员函数(methodToCall)的参数列表,而宏EXPORT_PC是用来记录当前程序计数器pc的位置的,用来帮助实现精确GC。关于精确GC(Extra/Precise GC),可以参考前面Dalvik虚拟机的启动过程分析一文的介绍。
宏GOTO_invokeMethod定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中,如下所示:
[cpp]view plaincopyprint?
1. #define GOTO_invokeMethod(_methodCallRange, _methodToCall, _vsrc1, _vdst) goto invokeMethod;
#define GOTO_invokeMethod(_methodCallRange, _methodToCall, _vsrc1, _vdst) goto invokeMethod;
它表示要跳到分支invokeMethod去解释当前类的成员函数_methodToCall,接下来我们就继续分析它的实现。
Step 8. invokeMethod
[cpp]view plaincopyprint?
1. GOTO_TARGET(invokeMethod, bool methodCallRange, const Method* _methodToCall,
2. u2 count, u2 regs)
3. {
4. STUB_HACK(vsrc1 = count; vdst = regs; methodToCall = _methodToCall;);
5. StackSaveArea* newSaveArea;
6. u4* newFp;
7. ......
8.
9. newFp = (u4*) SAVEAREA_FROM_FP(fp) - methodToCall->registersSize;
10. newSaveArea = SAVEAREA_FROM_FP(newFp);
11. ......
12.
13. newSaveArea->prevFrame = fp;
14. newSaveArea->savedPc = pc;
15. ......
16.
17. if (!dvmIsNativeMethod(methodToCall)) {
18. /*
19. * "Call" interpreted code. Reposition the PC, update the
20. * frame pointer and other local state, and continue.
21. */
22. curMethod = methodToCall;
23. methodClassDex = curMethod->clazz->pDvmDex;
24. pc = methodToCall->insns;
25. fp = self->curFrame = newFp;
26. ......
27.
28. FINISH(0); // jump to method start
29. } else {
30. /* set this up for JNI locals, even if not a JNI native */
31. ......
32.
33. self->curFrame = newFp;
34. ......
35.
36. /*
37. * Jump through native call bridge. Because we leave no
38. * space for locals on native calls, "newFp" points directly
39. * to the method arguments.
40. */
41. (*methodToCall->nativeFunc)(newFp, &retval, methodToCall, self);
42. ......
43.
44. if (true /*invokeInstr >= OP_INVOKE_VIRTUAL &&
45. invokeInstr <= OP_INVOKE_INTERFACE*/)
46. {
47. FINISH(3);
48. }
49.
50. ......
51. }
52.
53. ......
54. }
55. GOTO_TARGET_END
GOTO_TARGET(invokeMethod, bool methodCallRange, const Method* _methodToCall,
u2 count, u2 regs)
{
STUB_HACK(vsrc1 = count; vdst = regs; methodToCall = _methodToCall;);
StackSaveArea* newSaveArea;
u4* newFp;
......
newFp = (u4*) SAVEAREA_FROM_FP(fp) - methodToCall->registersSize;
newSaveArea = SAVEAREA_FROM_FP(newFp);
......
newSaveArea->prevFrame = fp;
newSaveArea->savedPc = pc;
......
if (!dvmIsNativeMethod(methodToCall)) {
/*
* "Call" interpreted code. Reposition the PC, update the
* frame pointer and other local state, and continue.
*/
curMethod = methodToCall;
methodClassDex = curMethod->clazz->pDvmDex;
pc = methodToCall->insns;
fp = self->curFrame = newFp;
......
FINISH(0); // jump to method start
} else {
/* set this up for JNI locals, even if not a JNI native */
......
self->curFrame = newFp;
......
/*
* Jump through native call bridge. Because we leave no
* space for locals on native calls, "newFp" points directly
* to the method arguments.
*/
(*methodToCall->nativeFunc)(newFp, &retval, methodToCall, self);
......
if (true /*invokeInstr >= OP_INVOKE_VIRTUAL &&
invokeInstr <= OP_INVOKE_INTERFACE*/)
{
FINISH(3);
}
......
}
......
}
GOTO_TARGET_END
分支invokeMethod通过宏GOTO_TARGET定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中。
分支invokeMethod首先是为当前要解释的成员函数methodToCall创建一个新的栈帧newFp,接着再通过调用函数dvmIsNativeMethod来判断成员函数methodToCall是否是一个JNI方法。
在新创建的栈帧newFp中,分别在其成员变量prevFrame和savedPc中保存了当前栈帧fp和当前程序计数器pc的值,这样使得在调用完成员函数methodToCall之后,可以返回到当前正在执行的成员函数的下一条指令中去,以及恢复当前线程的栈帧。
如果成员函数methodToCall不是一个JNI方法,那么就说明接下来仍然是要通过Dalvik虚拟机解释器来执行它。不过这时候需要将当前线程激活的栈帧fp设置为newFp,以及将程序计数器pc指向成员函数methodToCall的方法区。最后通过宏FINISH(0)来跳出当前分支,实际上就是跳出前面Step 6的switch语句,然后重复执行while循环语句。此时传递给宏FINISH的参数为0,表示不需要调整程序计数器pc的值,因为前面已经调整过了。
如果成员函数methodToCall是一个JNI方法,那么该JNI方法的地址就保存在methodToCall->nativeFunc中,这时候只需要直接对它进行调用即可。调用JNI方法结束之后,需要通过宏FINISH(3)调整程序计数器pc的值,以及跳出当前分支,以及可以回到前面Step 6的while循环去执行下一条指令。此时传递给宏FINISH的参数为3,表示要将程序计数器pc的值增加3,正好是跳过当前执行的指令。
宏FINISH定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中,如下所示:
[cpp]view plaincopyprint?
1. # define ADJUST_PC(_offset) do { \
2. pc += _offset; \
3. EXPORT_EXTRA_PC(); \
4. } while (false)
5. #endif
6.
7. # define FINISH(_offset) { ADJUST_PC(_offset); break; }
# define ADJUST_PC(_offset) do { \
pc += _offset; \
EXPORT_EXTRA_PC(); \
} while (false)
#endif
# define FINISH(_offset) { ADJUST_PC(_offset); break; }
注意,在宏ADJUST_PC中,又会通过宏EXPORT_EXTRA_PC来记录当前程序计数器pc的值,也是用来帮助实现精确GC的。
这一步执行完成之后,就回到前面的Step 6的while循环中,继续执行下一条指令,直到遇到reutrn指令为止。接下来我们就以OP_RETURN指令为例,来说明Java函数的返回操作,也就分析分支returnFromMethod的实现。
Step 9. returnFromMethod
[cpp]view plaincopyprint?
1. GOTO_TARGET(returnFromMethod)
2. {
3. StackSaveArea* saveArea;
4. ......
5.
6. saveArea = SAVEAREA_FROM_FP(fp);
7. ......
8.
9. fp = saveArea->prevFrame;
10. ......
11.
12. /* update thread FP, and reset local variables */
13. self->curFrame = fp;
14. curMethod = SAVEAREA_FROM_FP(fp)->method;
15. //methodClass = curMethod->clazz;
16. methodClassDex = curMethod->clazz->pDvmDex;
17. pc = saveArea->savedPc;
18. ......
19.
20. if (true /*invokeInstr >= OP_INVOKE_VIRTUAL &&
21. invokeInstr <= OP_INVOKE_INTERFACE*/)
22. {
23. FINISH(3);
24. }
25.
26. ......
27. }
28. GOTO_TARGET_END
GOTO_TARGET(returnFromMethod)
{
StackSaveArea* saveArea;
......
saveArea = SAVEAREA_FROM_FP(fp);
......
fp = saveArea->prevFrame;
......
/* update thread FP, and reset local variables */
self->curFrame = fp;
curMethod = SAVEAREA_FROM_FP(fp)->method;
//methodClass = curMethod->clazz;
methodClassDex = curMethod->clazz->pDvmDex;
pc = saveArea->savedPc;
......
if (true /*invokeInstr >= OP_INVOKE_VIRTUAL &&
invokeInstr <= OP_INVOKE_INTERFACE*/)
{
FINISH(3);
}
......
}
GOTO_TARGET_END
分支returnFromMethod通过宏GOTO_TARGET定义在文件dalvik/vm/mterp/out/InterpC-portstd.c中。
分支returnFromMethod的实现比较简单,它主要就是恢复上一个执行的成员函数的栈帧,以及该成员函数下一条要执行的指令。由于在前面的Step 8中,我们在当前栈帧中保存了上一个执行的成员函数的下一条要执行的指令及其栈帧,因此,这里对它们进行恢复是很直觉的。
不过有一点需要注意的是,前面的Step 8保存的是当前正在执行的成员函数的程序计算器,现在由于该程序计算器所指向的指令已经执行完成了,因此,我们需要继续调整从当前栈帧中恢复回来的指令值,使得它指向的是上一个执行的成员函数的下一条要执行的指令的值,这是通过宏FINISH(3)来完成的。
至此,我们就分析完成Dalvik虚拟机解释器的执行过程了,这个过程也就相当于是Dalvik虚拟机的运行过程,也就是说,Step 7到Step 9实际上是会不断地重复执行,直至进程退出为止的。以Zygote进程为例,Dalvik虚拟机解释器就是以com.android.internal.os.ZygoteInit类的静态成员函数main为入口点执行,然后在一个Socket上进行循环,用来等待和处理ActivityManagerService服务向它发送创建新应用程序进程的请求,直至系统退出为止。又以Android应用程序进程为例,Dalvik虚拟机解释器就是以android.app.ActivityThread类的静态成员函数main为入口点执行,然后在一消息队列上进行循环,用来等待和处理主线程的消息,直到应用程序退出为止。
当然,Dalvik虚拟机在运行的过程中,除了解释执行之外,还可能会进行JIT。JIT的目的就是将Java代码即时编译成Native代码之后再直接执行,这样对于经常运行的代码来说,可以提高性能。由于在将Java代码即时编译成Native代码的过程中,可以进一步利用运行时信息来进行激进优化,因此,JIT获得的Native代码比AOT获得的Native可能会更优化。关于JIT的实现,可以参考Hello, JIT World: The Joy of Simple JITs一文。
此外,从Step 4和Step 8可以知道,Dalvik虚拟机在运行的过程中,除了需要执行Java函数之外,还可能需要执行Native函数,这些Native函数也就是我们平时所说的JNI方法。在接下来的一篇文章中,我们就将分析这些JNI方法注册到Dalvik虚拟机里面去的,敬请关注!
- Dalvik虚拟机简要介绍转载
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机垃圾收集机制简要介绍
- Dalvik虚拟机简要介绍和学习计划
- [转载] JAVA虚拟机、Dalvik虚拟机和ART虚拟机简要对比
- Dalvik虚拟机垃圾收集机制简要介绍和学习计划
- java的类型转换
- 省市县 三级联动 sql语句 与你共分享 2(纯sql脚本)
- HDU 2602 OJ Bone Collector
- 反驳李刚的针对我吐槽的回应,曝光疯狂java李刚的骗子行径
- 如何终止特定 RAC 实例上的 session
- Dalvik虚拟机简要介绍转载
- 套接字地址结构
- 浏览器插件之ActiveX开发(三)
- Tomcat6.x配置及使用连接池
- 结构体字节对齐
- Redis资料网址
- 让ProgressDialog在setCancelable(false)时按返回键可dismiss
- Windows CPU参数
- 实用技巧:Google 搜索打不开的解决方法【图文教程】