Android运行时ART加载类和方法的过程分析.docx
- 文档编号:3485476
- 上传时间:2022-11-23
- 格式:DOCX
- 页数:25
- 大小:69.86KB
Android运行时ART加载类和方法的过程分析.docx
《Android运行时ART加载类和方法的过程分析.docx》由会员分享,可在线阅读,更多相关《Android运行时ART加载类和方法的过程分析.docx(25页珍藏版)》请在冰豆网上搜索。
Android运行时ART加载类和方法的过程分析
Android运行时ART加载类和方法的过程分析
在前一篇文章中,我们通过分析OAT文件的加载过程,认识了OAT文件的格式,其中包含了原始的DEX文件。
既然ART运行时执行的都是翻译DEX字节码后得到的本地机器指令了,为什么还需要在OAT文件中包含DEX文件,并且将它加载到内存去呢?
这是因为ART运行时提供了Java虚拟机接口,而要实现Java虚拟机接口不得不依赖于DEX文件。
本文就通过分析ART运行时加载类及其方法的过程来理解DEX文件的作用。
在前面这篇文章的最后,我们简单总结了ART运行时查找类方法的本地机器指令的过程,如图1所示:
为了方便描述,我们将DEX文件中描述的类和方法称为DEX类(DexClass)和DEX方法(DexMethod),而将在OAT文件中描述的类和方法称为OAT类(OatClass)和OAT方法(OatMethod)。
接下来我们还会看到,ART运行时在内部又会使用另外两个不同的术语来描述类和方法,其中将类描述为Class,而将类方法描述为ArtMethod。
在图1中,为了找到一个类方法的本地机器指令,我们需要执行以下的操作:
1.在DEX文件中找到目标DEX类的编号,并且以这个编号为索引,在OAT文件中找到对应的OAT类。
2.在DEX文件中找到目标DEX方法的编号,并且以这个编号为索引,在上一步找到的OAT类中找到对应的OAT方法。
3.使用上一步找到的OAT方法的成员变量begin_和code_offset_,计算出该方法对应的本地机器指令。
通过前面一文的学习,我们可以知道,ART运行时的入口是com.Android.internal.os.ZygoteInit类的静态成员函数main,如下所示:
[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);
......
}
}
......
}
这个函数定义在文件frameworks/base/core/jni/AndroidRuntime.cpp中。
在AndroidRuntime类的成员函数start中,首先是通过调用函数startVm创建了一个Java虚拟机mJavaVM及其JNI接口env。
这个Java虚拟机实际上就是ART运行时。
在接下来的描述中,我们将不区分ART虚拟机和ART运行时,并且认为它们表达的是同一个概念。
获得了ART虚拟机的JNI接口之后,就可以通过它提供的函数FindClass和GetStaticMethodID来加载com.android.internal.os.ZygoteInit类及其静态成员函数main。
于是,最后就可以再通过JNI接口提供的函数CallStaticVoidMethod来调用com.android.internal.os.ZygoteInit类的静态成员函数main,以及进行到ART虚拟机里面去运行。
接下来,我们就通过分析JNI接口FindClass和GetStaticMethodID的实现,以便理解ART运行时是如何查找到指定的类和方法的。
在接下来的一篇文章中,我们再分析ART运行时是如何通过JNI接口CallStaticVoidMethod来执行指定类方法的本地机器指令的。
在分析JNI接口FindClass和GetStaticMethodID的实现之前,我们先要讲清楚JNI接口是如何创建的。
从前面一文可以知道,与ART虚拟机主线程关联的JNI接口是在函数JNI_CreateJavaVM中创建的,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
extern"C"jintJNI_CreateJavaVM(JavaVM**p_vm,JNIEnv**p_env,void*vm_args){
......
*p_env=Thread:
:
Current()->GetJniEnv();
......
returnJNI_OK;
}
这个函数定义在文件art/runtime/jni_internal.cc中。
调用Thread类的静态成员函数Current获得的是用来描述当前线程(即ART虚拟机的主线程)的一个Thread对象,再通过调用这个Thread对象的成员函数GetJniEnv就获得一个JNI接口,并且保存在输出参数p_env中。
Thread类的成员函数GetJniEnv的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
classPACKED(4)Thread{
public:
......
//JNImethods
JNIEnvExt*GetJniEnv()const{
returnjni_env_;
}
......
private:
......
//EverythreadmayhaveanassociatedJNIenvironment
JNIEnvExt*jni_env_;
......
};
这个函数定义在文件art/runtime/thread.h中。
Thread类的成员函数GetJniEnv返回的是成员变量jni_env_指向的一个JNIEnvExt对象。
JNIEnvExt类是从JNIEnv类继承下来的,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
structJNIEnvExt:
publicJNIEnv{
......
};
这个类定义在文件art/runtime/jni_internal.h。
JNIEnv类定义了JNI接口,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
typedef_JNIEnvJNIEnv;
......
struct_JNIEnv{
/*donotrenamethis;itdoesnotseemtobeentirelyopaque*/
conststructJNINativeInterface*functions;
......
jintGetVersion()
{returnfunctions->GetVersion(this);}
......
};
这个类定义在文件libnativehelper/include/nativehelper/jni.h中。
在JNIEnv类中,最重要的就是成员变量functions了,它指向的是一个类型为JNINativeInterface的JNI函数表。
所有的JNI接口调用都是通过这个JNI函数表来实现的。
例如,用来获得版本号的JNI接口GetVersion就是通过调用JNI函数表中的GetVersion函数来实现的。
那么,上述的JNI函数表是如何创建的呢?
通过JNIEnvExt类的构造函数可以知道答案,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
JNIEnvExt:
:
JNIEnvExt(Thread*self,JavaVMExt*vm)
:
......{
functions=unchecked_functions=&gJniNativeInterface;
......
}
这个函数定义在文件art/runtime/jni_internal.cc中。
JNIEnvExt类的构造函数将父类JNIEnv的成员变量functions初始化为全局变量gJniNativeInterface。
也就是说,JNI函数表实际是由全局变量gJniNativeInterface来描述的。
全局变量gJniNativeInterface的定义如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
constJNINativeInterfacegJniNativeInterface={
NULL,//reserved0.
NULL,//reserved1.
NULL,//reserved2.
NULL,//reserved3.
JNI:
:
GetVersion,
......
JNI:
:
FindClass,
......
JNI:
:
GetStaticMethodID,
......
JNI:
:
CallStaticVoidMethod,
......
};
这个全局变量定义在文件art/runtime/jni_internal.cc中。
从这里可以看出,JNI函数表实际上是由JNI类的静态成员函数组成的。
例如,JNI函数GetVersion是由JNI类的静态成员函数GetVersion来实现的。
理解了这一点之后,我们就轻松地知道同接下来我们要分析的JNI接口FindClass和GetStaticMethodID分别是由JNI类的静态成员函数FindClass和GetStaticMethodID来实现的。
事实上,如果读者看过这篇文章,那么对上述的JNI接口定义是一目了然的。
JNI类的静态成员函数FindClass的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
classJNI{
public:
......
staticjclassFindClass(JNIEnv*env,constchar*name){
CHECK_NON_NULL_ARGUMENT(FindClass,name);
Runtime*runtime=Runtime:
:
Current();
ClassLinker*class_linker=runtime->GetClassLinker();
std:
:
stringdescriptor(NormalizeJniClassDescriptor(name));
ScopedObjectAccesssoa(env);
Class*c=NULL;
if(runtime->IsStarted()){
ClassLoader*cl=GetClassLoader(soa);
c=class_linker->FindClass(descriptor.c_str(),cl);
}else{
c=class_linker->FindSystemClass(descriptor.c_str());
}
returnsoa.AddLocalReference
}
......
};
这个函数定义在文件art/runtime/jni_internal.cc中。
在ART虚拟机进程中,存在着一个Runtime单例,用来描述ART运行时。
通过调用Runtime类的静态成员函数Current可以获得上述Runtime单例。
获得了这个单例之后,就可以调用它的成员函数GetClassLinker来获得一个ClassLinker对象。
从前面一文可以知道。
上述ClassLinker对象是在创建ART虚拟机的过程中创建的,用来加载类以及链接类方法。
JNI类的静态成员函数FindClass首先是判断ART运行时是否已经启动起来。
如果已经启动,那么就通过调用函数GetClassLoader来获得当前线程所关联的ClassLoader,并且以此为参数,调用前面获得的ClassLinker对象的成员函数FindClass来加载由参数name指定的类。
一般来说,当前线程所关联的ClassLoader就是当前正在执行的类方法所关联的ClassLoader,即用来加载当前正在执行的类的ClassLoader。
如果ART虚拟机还没有开始执行类方法,就像我们现在这个场景,那么当前线程所关联的ClassLoader实际上就系统类加载器,即SystemClassLoader。
如果ART运行时还没有启动,那么这时候只可以加载系统类。
这个通过前面获得的ClassLinker对象的成员函数FindSystemClass来实现的。
在我们这个场景中,ART运行时已经启动,因此,接下来我们就继续分析ClassLinker类的成员函数FindClass的实现。
ClassLinker类的成员函数FindClass的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
mirror:
:
Class*ClassLinker:
:
FindClass(constchar*descriptor,mirror:
:
ClassLoader*class_loader){
......
Thread*self=Thread:
:
Current();
......
//Findtheclassintheloadedclassestable.
mirror:
:
Class*klass=LookupClass(descriptor,class_loader);
if(klass!
=NULL){
returnEnsureResolved(self,klass);
}
//Classisnotyetloaded.
if(descriptor[0]=='['){
......
}elseif(class_loader==NULL){
DexFile:
:
ClassPathEntrypair=DexFile:
:
FindInClassPath(descriptor,boot_class_path_);
if(pair.second!
=NULL){
returnDefineClass(descriptor,NULL,*pair.first,*pair.second);
}
}elseif(Runtime:
:
Current()->UseCompileTimeClassPath()){
......
}else{
ScopedObjectAccessUncheckedsoa(self->GetJniEnv());
ScopedLocalRef
soa.AddLocalReference
std:
:
stringclass_name_string(DescriptorToDot(descriptor));
ScopedLocalRef
{
ScopedThreadStateChangetsc(self,kNative);
ScopedLocalRef
soa.Env()->NewStringUTF(class_name_string.c_str()));
if(class_name_object.get()==NULL){
returnNULL;
}
CHECK(class_loader_object.get()!
=NULL);
result.reset(soa.Env()->CallObjectMethod(class_loader_object.get(),
WellKnownClasses:
:
java_lang_ClassLoader_loadClass,
class_name_object.get()));
}
if(soa.Self()->IsExceptionPending()){
//IftheClassLoaderthrew,passthatexceptionup.
returnNULL;
}elseif(result.get()==NULL){
//brokenloader-throwNPEtobecompatiblewithDalvik
ThrowNullPointerException(NULL,StringPrintf("ClassLoader.loadClassreturnednullfor%s",
class_name_string.c_str()).c_str());
returnNULL;
}else{
//success,returnmirror:
:
Class*
returnsoa.Decode : Class*>(result.get()); } } ThrowNoClassDefFoundError("Class%snotfound",PrintableString(descriptor).c_str()); returnNULL; } 这个函数定义在文件art/runtime/class_linker.cc中。 参数descriptor指向的是要加载的类的签名,而参数class_loader指向的是一个类加载器,我们假设它的值不为空,并且指向系统类加载器。 ClassLinker类的成员函数FindClass首先是调用另外一个成员函数LookupClass来检查参数descriptor指定的类是否已经被加载过。 如果是的话,那么ClassLinker类的成员函数LookupClass就会返回一个对应的Class对象,这个Class对象接着就会返回给调用者,表示加载已经完成。 如果参数descriptor指定的类还没有被加载过,这时候主要就是要看参数class_loader的值了。 如果参数class_loader的值等于NULL,那么就需要调用DexFile类的静态FindInClassPath来在系统启动类路径寻找对应的类。 一旦寻找到,那么就会获得包含目标类的DEX文件,因此接下来就调用ClassLinker类的另外一个成员函数DefineClass从获得的DEX文件中加载参数descriptor指定的类了。 如果参数class_loader的值不等于NULL,也就是说ClassLinker类的成员函数FindClass的调用者指定了类加载器,那么就通过该类加载器来加载参数descriptor指定的类。 每一个类加载器在Java层都对应有一个java.lang.ClassLoader对象。 通过调用这个java.lang.ClassLoader类的成员函数loadClass即可加载指定的类。 在我们这个场景中,上述的java.lang.ClassLoader类是一个系统类加载器,它负责加载系统类。 而我们当前要加载的类为com.android.internal.os.ZygoteInit,它属于一个系统类。 系统类加载器在加载系统类实际上也是通过JNI方法调用ClassLinker类的成员函数FindClass来实现的。 只不过这时候传进来的参数class_loader是一个NULL值。 这样,ClassLinker类的成员函数FindClass就会在系统启动类路径中寻找参数descriptor指定的类可以在哪一个DEX文件加载,这是通过调用DexFile类的静态成员函数FindInClassPath来实现的。 所谓的系统启动类路径,其实就是一系列指定的由系统提供的DEX文件,这些DEX文件保存在ClassLinker类的成员变量boot_class_path_描述的一个向量中。 那么问题就来了,这些DEX文件是怎么来的呢? 我们知道,在ART运行时中,我们使用的是OAT文件。 如果看过前面这篇文章,就会很容易知道,OAT文件里面包含有DEX文件。 而且ART运行时在启动的时候,会加载一个名称为system@framework@boot.art@classes.oat的OAT文件。 这个OAT文件包含有多个DEX文件,每一个DEX文件都是一个系统启动类路径,它们会被添加到ClassLinker类的成员变量boot_class_path_描述的向量中去。 这里调用DexFile类的静态成员函数FindInClassPath,实际要完成的工作就是从ClassLinker类的成员变量boot_class_path_描述的一系列的DEX文件中检查哪一个DEX文件包含有参数descriptor指定的类。 这可以通过解析DEX文件来实现,关于DEX文件的格式,可以参考官方文档: 。 知道了参数descri
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Android 运行 ART 加载 方法 过程 分析
![提示](https://static.bdocx.com/images/bang_tan.gif)