1、AndroidART运行时无缝替换Dalvik虚拟机的过程分析资料Android ART运行时无缝替换Dalvik虚拟机的过程分析Android 4.4发布了一个ART运行时,准备用来替换掉之前一直使用的Dalvik虚拟机,希望籍此解决饱受诟病的性能问题。老罗不打算分析ART的实现原理,只是很有兴趣知道ART是如何无缝替换掉原来的Dalvik虚拟机的。毕竟在原来的系统中,大量的代码都是运行在Dalvik虚拟机里面的。开始觉得这个替换工作是挺复杂的,但是分析了相关代码之后,发现思路是很清晰的。本文就详细分析这个无缝的替换过程。我们知道,Dalvik虚拟机实则也算是一个Java虚拟机,只不过它执行
2、的不是class文件,而是dex文件。因此,ART运行时最理想的方式也是实现为一个Java虚拟机的形式,这样就可以很容易地将Dalvik虚拟机替换掉。注意,我们这里说实现为Java虚拟机的形式,实际上是指提供一套完全与Java虚拟机兼容的接口。例如,Dalvik虚拟机在接口上与Java虚拟机是一致的,但是它的内部可以是完全不一样的东西。 实际上,ART运行时就是真的和Dalvik虚拟机一样,实现了一套完全兼容Java虚拟机的接口。为了方便描述,接下来我们就将ART运行时称为ART虚拟机,它和Dalvik虚拟机、Java虚拟机的关系如图1所示:从图1可以知道,Dalvik虚拟机和ART虚拟机都实
3、现了三个用来抽象Java虚拟机的接口: 1. JNI_GetDefaultJavaVMInitArgs - 获取虚拟机的默认初始化参数 2. JNI_CreateJavaVM - 在进程中创建虚拟机实例 3. JNI_GetCreatedJavaVMs - 获取进程中创建的虚拟机实例 在Android系统中,Davik虚拟机实现在libdvm.so中,ART虚拟机实现在libart.so中。也就是说,libdvm.so和libart.so导出了JNI_GetDefaultJavaVMInitArgs、JNI_CreateJavaVM和JNI_GetCreatedJavaVMs这三个接口,供外界
4、调用。 此外,Android系统还提供了一个系统属性persist.sys.dalvik.vm.lib,它的值要么等于libdvm.so,要么等于libart.so。当等于libdvm.so时,就表示当前用的是Dalvik虚拟机,而当等于libart.so时,就表示当前用的是ART虚拟机。 以上描述的Dalvik虚拟机和ART虚拟机的共同之处,当然它们之间最显著还是不同之处。不同的地方就在于,Dalvik虚拟机执行的是dex字节码,ART虚拟机执行的是本地机器码。这意味着Dalvik虚拟机包含有一个解释器,用来执行dex字节码,具体可以参考这个系列的文章。当然,Android从2.2开始,也包
5、含有JIT(Just-In-Time),用来在运行时动态地将执行频率很高的dex字节码翻译成本地机器码,然后再执行。通过JIT,就可以有效地提高Dalvik虚拟机的执行效率。但是,将dex字节码翻译成本地机器码是发生在应用程序的运行过程中的,并且应用程序每一次重新运行的时候,都要做重做这个翻译工作的。因此,即使用采用了JIT,Dalvik虚拟机的总体性能还是不能与直接执行本地机器码的ART虚拟机相比。 那么,ART虚拟机执行的本地机器码是从哪里来的呢?Android的运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者要将重新将自己的应用直接编译成目标机器码。也就是说,开发者开发出的应
6、用程序经过编译和打包之后,仍然是一个包含dex字节码的APK文件。既然应用程序包含的仍然是dex字节码,而ART虚拟机需要的是本地机器码,这就必然要有一个翻译的过程。这个翻译的过程当然不能发生应用程序运行的时候,否则的话就和Dalvik虚拟机的JIT一样了。在计算机的世界里,与JIT相对的是AOT。AOT进Ahead-Of-Time的简称,它发生在程序运行之前。我们用静态语言(例如C/C+)来开发应用程序的时候,编译器直接就把它们翻译成目标机器码。这种静态语言的编译方式也是AOT的一种。但是前面我们提到,ART虚拟机并不要求开发者将自己的应用直接编译成目标机器码。这样,将应用的dex字节码翻译
7、成本地机器码的最恰当AOT时机就发生在应用安装的时候。 我们知道,没有ART虚拟机之前,应用在安装的过程,其实也会执行一次“翻译”的过程。只不过这个“翻译”的过程是将dex字节码进行优化,也就是由dex文件生成odex文件。这个过程由安装服务PackageManagerService请求守护进程installd来执行的。从这个角度来说,在应用安装的过程中将dex字节码翻译成本地机器码对原来的应用安装流程基本上就不会产生什么影响。 有了以上的背景知识之后,我们接下来就从两个角度来了解ART虚拟机是如何做到无缝替换Dalvik虚拟机的: 1. ART虚拟机的启动过程; 2. Dex字节码翻译成本地
8、机器码的过程。 我们知道,Android系统在启动的时候,会创建一个Zygote进程,充当应用程序进程孵化器。Zygote进程在启动的过程中,又会创建一个Dalvik虚拟机。Zygote进程是通过复制自己来创建新的应用程序进程的。这意味着Zygote进程会将自己的Dalvik虚拟机复制给应用程序进程。通过这种方式就可以大大地提高应用程序的启动速度,因为这种方式避免了每一个应用程序进程在启动的时候都要去创建一个Dalvik。事实上,Zygote进程通过自我复制的方式来创建应用程序进程,省去的不仅仅是应用程序进程创建Dalvik虚拟机的时间,还能省去应用程序进程加载各种系统库和系统资源的时间,因为
9、它们在Zygote进程中已经加载过了,并且也会连同Dalvik虚拟机一起复制到应用程序进程中去。关于Zygote进程和应用程序进程启动的更多知识,可以参考和这两篇文章。 即然应用程序进程里面的Dalvik虚拟机都是从Zygote进程中复制过来的,那么接下来我们就继续Zygote进程是如何创建Dalvik虚拟机的。从这篇文章可以知道,Zygote进程中的Dalvik虚拟机是从AndroidRuntime:start这个函数开始创建的。因此,接下来我们就看看这个函数的实现:cpp view plain copy 在CODE上查看代码片派生到我的代码片void AndroidRuntime:star
10、t(const char* className, const char* options) . /* start the virtual machine */ JniInvocation jni_invocation; jni_invocation.Init(NULL); JNIEnv* env; if (startVm(&mJavaVM, &env) != 0) return; . /* * Start VM. This thread becomes the main thread of the VM, and will * not return until the VM exits. */
11、 char* slashClassName = toSlashClassName(className); jclass startClass = env-FindClass(slashClassName); if (startClass = NULL) ALOGE(JavaVM unable to locate class %sn, slashClassName); /* keep going */ else jmethodID startMeth = env-GetStaticMethodID(startClass, main, (Ljava/lang/String;)V); if (sta
12、rtMeth = NULL) ALOGE(JavaVM unable to find main() in %sn, className); /* keep going */ else env-CallStaticVoidMethod(startClass, startMeth, strArray); #if 0 if (env-ExceptionCheck() threadExitUncaughtException(env); #endif . 这个函数定义在文件frameworks/base/core/jni/AndroidRuntime.cpp中。 AndroidRuntime类的成员函数
13、start最主要是做了以下三件事情: 1. 创建一个JniInvocation实例,并且调用它的成员函数init来初始化JNI环境; 2. 调用AndroidRuntime类的成员函数startVm来创建一个虚拟机及其对应的JNI接口,即创建一个JavaVM接口和一个JNIEnv接口; 3. 有了上述的JavaVM接口和JNIEnv接口之后,就可以在Zygote进程中加载指定的class了。 其中,第1件事情和第2件事情又是最关键的。因此,接下来我们继续分析它们所对应的函数的实现。 JniInvocation类的成员函数init的实现如下所示:cpp view plain copy 在CODE
14、上查看代码片派生到我的代码片#ifdef HAVE_ANDROID_OS static const char* kLibrarySystemProperty = persist.sys.dalvik.vm.lib; #endif static const char* kLibraryFallback = libdvm.so; bool JniInvocation:Init(const char* library) #ifdef HAVE_ANDROID_OS char default_libraryPROPERTY_VALUE_MAX; property_get(kLibrarySystemP
15、roperty, default_library, kLibraryFallback); #else const char* default_library = kLibraryFallback; #endif if (library = NULL) library = default_library; handle_ = dlopen(library, RTLD_NOW); if (handle_ = NULL) if (strcmp(library, kLibraryFallback) = 0) / Nothing else to try. ALOGE(Failed to dlopen %s: %s, library, dlerror(); return false; / Note that this is enough to get something like the zygote / running, we cant property_set here to fix this for the
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1