Android ART运行时无缝替换Dalvik虚拟机的过程分析.docx
- 文档编号:4518238
- 上传时间:2023-05-07
- 格式:DOCX
- 页数:16
- 大小:83.43KB
Android ART运行时无缝替换Dalvik虚拟机的过程分析.docx
《Android ART运行时无缝替换Dalvik虚拟机的过程分析.docx》由会员分享,可在线阅读,更多相关《Android ART运行时无缝替换Dalvik虚拟机的过程分析.docx(16页珍藏版)》请在冰点文库上搜索。
AndroidART运行时无缝替换Dalvik虚拟机的过程分析
AndroidART运行时无缝替换Dalvik虚拟机的过程分析
Android4.4发布了一个ART运行时,准备用来替换掉之前一直使用的Dalvik虚拟机,希望籍此解决饱受诟病的性能问题。
老罗不打算分析ART的实现原理,只是很有兴趣知道ART是如何无缝替换掉原来的Dalvik虚拟机的。
毕竟在原来的系统中,大量的代码都是运行在Dalvik虚拟机里面的。
开始觉得这个替换工作是挺复杂的,但是分析了相关代码之后,发现思路是很清晰的。
本文就详细分析这个无缝的替换过程。
我们知道,Dalvik虚拟机实则也算是一个Java虚拟机,只不过它执行的不是class文件,而是dex文件。
因此,ART运行时最理想的方式也是实现为一个Java虚拟机的形式,这样就可以很容易地将Dalvik虚拟机替换掉。
注意,我们这里说实现为Java虚拟机的形式,实际上是指提供一套完全与Java虚拟机兼容的接口。
例如,Dalvik虚拟机在接口上与Java虚拟机是一致的,但是它的内部可以是完全不一样的东西。
实际上,ART运行时就是真的和Dalvik虚拟机一样,实现了一套完全兼容Java虚拟机的接口。
为了方便描述,接下来我们就将ART运行时称为ART虚拟机,它和Dalvik虚拟机、Java虚拟机的关系如图1所示:
从图1可以知道,Dalvik虚拟机和ART虚拟机都实现了三个用来抽象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这三个接口,供外界调用。
此外,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开始,也包含有JIT(Just-In-Time),用来在运行时动态地将执行频率很高的dex字节码翻译成本地机器码,然后再执行。
通过JIT,就可以有效地提高Dalvik虚拟机的执行效率。
但是,将dex字节码翻译成本地机器码是发生在应用程序的运行过程中的,并且应用程序每一次重新运行的时候,都要做重做这个翻译工作的。
因此,即使用采用了JIT,Dalvik虚拟机的总体性能还是不能与直接执行本地机器码的ART虚拟机相比。
那么,ART虚拟机执行的本地机器码是从哪里来的呢?
Android的运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者要将重新将自己的应用直接编译成目标机器码。
也就是说,开发者开发出的应用程序经过编译和打包之后,仍然是一个包含dex字节码的APK文件。
既然应用程序包含的仍然是dex字节码,而ART虚拟机需要的是本地机器码,这就必然要有一个翻译的过程。
这个翻译的过程当然不能发生应用程序运行的时候,否则的话就和Dalvik虚拟机的JIT一样了。
在计算机的世界里,与JIT相对的是AOT。
AOT进Ahead-Of-Time的简称,它发生在程序运行之前。
我们用静态语言(例如C/C++)来开发应用程序的时候,编译器直接就把它们翻译成目标机器码。
这种静态语言的编译方式也是AOT的一种。
但是前面我们提到,ART虚拟机并不要求开发者将自己的应用直接编译成目标机器码。
这样,将应用的dex字节码翻译成本地机器码的最恰当AOT时机就发生在应用安装的时候。
我们知道,没有ART虚拟机之前,应用在安装的过程,其实也会执行一次“翻译”的过程。
只不过这个“翻译”的过程是将dex字节码进行优化,也就是由dex文件生成odex文件。
这个过程由安装服务PackageManagerService请求守护进程installd来执行的。
从这个角度来说,在应用安装的过程中将dex字节码翻译成本地机器码对原来的应用安装流程基本上就不会产生什么影响。
有了以上的背景知识之后,我们接下来就从两个角度来了解ART虚拟机是如何做到无缝替换Dalvik虚拟机的:
1.ART虚拟机的启动过程;
2.Dex字节码翻译成本地机器码的过程。
我们知道,Android系统在启动的时候,会创建一个Zygote进程,充当应用程序进程孵化器。
Zygote进程在启动的过程中,又会创建一个Dalvik虚拟机。
Zygote进程是通过复制自己来创建新的应用程序进程的。
这意味着Zygote进程会将自己的Dalvik虚拟机复制给应用程序进程。
通过这种方式就可以大大地提高应用程序的启动速度,因为这种方式避免了每一个应用程序进程在启动的时候都要去创建一个Dalvik。
事实上,Zygote进程通过自我复制的方式来创建应用程序进程,省去的不仅仅是应用程序进程创建Dalvik虚拟机的时间,还能省去应用程序进程加载各种系统库和系统资源的时间,因为它们在Zygote进程中已经加载过了,并且也会连同Dalvik虚拟机一起复制到应用程序进程中去。
关于Zygote进程和应用程序进程启动的更多知识,可以参考和这两篇文章。
即然应用程序进程里面的Dalvik虚拟机都是从Zygote进程中复制过来的,那么接下来我们就继续Zygote进程是如何创建Dalvik虚拟机的。
从这篇文章可以知道,Zygote进程中的Dalvik虚拟机是从AndroidRuntime:
:
start这个函数开始创建的。
因此,接下来我们就看看这个函数的实现:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidAndroidRuntime:
:
start(constchar*className,constchar*options)
{
......
/*startthevirtualmachine*/
JniInvocationjni_invocation;
jni_invocation.Init(NULL);
JNIEnv*env;
if(startVm(&mJavaVM,&env)!
=0){
return;
}
......
/*
*StartVM.ThisthreadbecomesthemainthreadoftheVM,andwill
*notreturnuntiltheVMexits.
*/
char*slashClassName=toSlashClassName(className);
jclassstartClass=env->FindClass(slashClassName);
if(startClass==NULL){
ALOGE("JavaVMunabletolocateclass'%s'\n",slashClassName);
/*keepgoing*/
}else{
jmethodIDstartMeth=env->GetStaticMethodID(startClass,"main",
"([Ljava/lang/String;)V");
if(startMeth==NULL){
ALOGE("JavaVMunabletofindmain()in'%s'\n",className);
/*keepgoing*/
}else{
env->CallStaticVoidMethod(startClass,startMeth,strArray);
#if0
if(env->ExceptionCheck())
threadExitUncaughtException(env);
#endif
}
}
......
}
这个函数定义在文件frameworks/base/core/jni/AndroidRuntime.cpp中。
AndroidRuntime类的成员函数start最主要是做了以下三件事情:
1.创建一个JniInvocation实例,并且调用它的成员函数init来初始化JNI环境;
2.调用AndroidRuntime类的成员函数startVm来创建一个虚拟机及其对应的JNI接口,即创建一个JavaVM接口和一个JNIEnv接口;
3.有了上述的JavaVM接口和JNIEnv接口之后,就可以在Zygote进程中加载指定的class了。
其中,第1件事情和第2件事情又是最关键的。
因此,接下来我们继续分析它们所对应的函数的实现。
JniInvocation类的成员函数init的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
#ifdefHAVE_ANDROID_OS
staticconstchar*kLibrarySystemProperty="persist.sys.dalvik.vm.lib";
#endif
staticconstchar*kLibraryFallback="libdvm.so";
boolJniInvocation:
:
Init(constchar*library){
#ifdefHAVE_ANDROID_OS
chardefault_library[PROPERTY_VALUE_MAX];
property_get(kLibrarySystemProperty,default_library,kLibraryFallback);
#else
constchar*default_library=kLibraryFallback;
#endif
if(library==NULL){
library=default_library;
}
handle_=dlopen(library,RTLD_NOW);
if(handle_==NULL){
if(strcmp(library,kLibraryFallback)==0){
//Nothingelsetotry.
ALOGE("Failedtodlopen%s:
%s",library,dlerror());
returnfalse;
}
//Notethatthisisenoughtogetsomethinglikethezygote
//running,wecan'tproperty_setheretofixthisforthefuture
//becausewearerootandnotthesystemuser.See
//RuntimeImonInitforwherewefixupthepropertyto
//avoidfuturefallbacks.http:
//b/11463182
ALOGW("Fallingbackfrom%sto%safterdlopenerror:
%s",
library,kLibraryFallback,dlerror());
library=kLibraryFallback;
handle_=dlopen(library,RTLD_NOW);
if(handle_==NULL){
ALOGE("Failedtodlopen%s:
%s",library,dlerror());
returnfalse;
}
}
if(!
FindSymbol(reinterpret_cast
"JNI_GetDefaultJavaVMInitArgs")){
returnfalse;
}
if(!
FindSymbol(reinterpret_cast
"JNI_CreateJavaVM")){
returnfalse;
}
if(!
FindSymbol(reinterpret_cast
"JNI_GetCreatedJavaVMs")){
returnfalse;
}
returntrue;
}
这个函数定义在文件libnativehelper/JniInvocation.cpp中。
JniInvocation类的成员函数init所做的事情很简单。
它首先是读取系统属性persist.sys.dalvik.vm.lib的值。
前面提到,系统属性persist.sys.dalvik.vm.lib的值要么等于libdvm.so,要么等于libart.so。
因此,接下来通过函数dlopen加载到进程来的要么是libdvm.so,要么是libart.so。
无论加载的是哪一个so,都要求它导出JNI_GetDefaultJavaVMInitArgs、JNI_CreateJavaVM和JNI_GetCreatedJavaVMs这三个接口,并且分别保存在JniInvocation类的三个成员变量JNI_GetDefaultJavaVMInitArgs_、JNI_CreateJavaVM_和JNI_GetCreatedJavaVMs_中。
这三个接口也就是前面我们提到的用来抽象Java虚拟机的三个接口。
从这里就可以看出,JniInvocation类的成员函数init实际上就是根据系统属性persist.sys.dalvik.vm.lib来初始化Dalvik虚拟机或者ART虚拟机环境。
接下来我们继续看AndroidRuntime类的成员函数startVm的实现:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
intAndroidRuntime:
:
startVm(JavaVM**pJavaVM,JNIEnv**pEnv)
{
......
/*
*InitializetheVM.
*
*TheJavaVM*isessentiallyper-process,andtheJNIEnv*isper-thread.
*Ifthiscallsucceeds,theVMisready,andwecanstartissuing
*JNIcalls.
*/
if(JNI_CreateJavaVM(pJavaVM,pEnv,&initArgs)<0){
ALOGE("JNI_CreateJavaVMfailed\n");
gotobail;
}
......
}
这个函数定义在文件frameworks/base/core/jni/AndroidRuntime.cpp中。
AndroidRuntime类的成员函数startVm最主要就是调用函数JNI_CreateJavaVM来创建一个JavaVM接口及其对应的JNIEnv接口:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
extern"C"jintJNI_CreateJavaVM(JavaVM**p_vm,JNIEnv**p_env,void*vm_args){
returnJniInvocation:
:
GetJniInvocation().JNI_CreateJavaVM(p_vm,p_env,vm_args);
}
这个函数定义在文件libnativehelper/JniInvocation.cpp中。
JniInvocation类的静态成员函数GetJniInvocation返回的便是前面所创建的JniInvocation实例。
有了这个JniInvocation实例之后,就继续调用它的成员函数JNI_CreateJavaVM来创建一个JavaVM接口及其对应的JNIEnv接口:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
jintJniInvocation:
:
JNI_CreateJavaVM(JavaVM**p_vm,JNIEnv**p_env,void*vm_args){
returnJNI_CreateJavaVM_(p_vm,p_env,vm_args);
}
这个函数定义在文件libnativehelper/JniInvocation.cpp中。
JniInvocation类的成员变量JNI_CreateJavaVM_指向的就是前面所加载的libdvm.so或者libart.so所导出的函数JNI_CreateJavaVM,因此,JniInvocation类的成员函数JNI_CreateJavaVM返回的JavaVM接口指向的要么是Dalvik虚拟机,要么是ART虚拟机。
通过上面的分析,我们就很容易知道,Android系统通过将ART运行时抽象成一个Java虚拟机,以及通过系统属性persist.sys.dalvik.vm.lib和一个适配层JniInvocation,就可以无缝地将Dalvik虚拟机替换为ART运行时。
这个替换过程设计非常巧妙,因为涉及到的代码修改是非常少的。
以上就是ART虚拟机的启动过程,接下来我们再分析应用程序在安装过程中将dex字节码翻译为本地机器码的过程。
Android应用程序的安装过程可以参考这篇文章。
简单来说,就是Android系统通过PackageManagerService来安装APK,在安装的过程,PackageManagerService会通过另外一个类Installer的成员函数dexopt来对APK里面的dex字节码进行优化:
[java]viewplaincopy在CODE上查看代码片派生到我的代码片
publicfinalclassInstaller{
......
publicintdexopt(StringapkPath,intuid,booleanisPublic){
StringBuilderbuilder=newStringBuilder("dexopt");
end('');
builder.append(apkPath);
builder.append('');
builder.append(uid);
builder.append(isPublic?
"1":
"0");
returnexecute(builder.toString());
}
......
}
这个函数定义在文件frameworks/base/services/java/com/android/server/pm/Installer.java中。
Installer通过socket向守护进程installd发送一个dexopt请求,这个请求是由installd里面的函数dexopt来处理的:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
intdexopt(constchar*apk_path,uid_tuid,intis_public)
{
structutimbufut;
structstatapk_stat,dex_stat;
charout_path[PKG_PATH_MAX];
chardexopt_flags[PROPERTY_VALUE_MAX];
charpersist_sys_dalvik_vm_lib[PROPERTY_VALUE_MAX];
char*end;
intres,zip_fd=-1,out_fd=-1;
......
/*Thecommandtorundependonesthevalueofpersist.sys.dalvik.vm.lib*/
property_get("persist.sys.dalvik.vm.lib",persist_sys_dalvik_vm_lib,"libdvm.so");
/*Beforeanythingelse:
istherea.odexfile?
Ifso,wehave
*precompiledtheapkandthereisnothingtodohere.
*/
sprintf(out_path,"%s%s",apk_path,".odex");
if(stat(out_path,&dex_stat)==0){
return0;
}
if(create_cache_path(out_path,apk_path)){
return-1;
}
......
out_fd=open(out_path,O_RDWR|O_CREAT|O_EXCL,0644);
......
pid_tpid;
pid=fork();
if(pid==0){
......
if(strncmp(persist_sys_dalvik_vm_lib,"libdvm",6)==0){
run_dexopt(zip_fd,out_fd,apk_path,out_path,dexopt_flags);
}elseif(strncmp(persist_sys_dalvik_vm_lib,"libart",6)==0){
run_dex2
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Android ART运行时无缝替换Dalvik虚拟机的过程分析 ART 运行 无缝 替换 Dalvik 虚拟机 过程 分析