Android运行时ART加载类和方法的过程分析资料_第1页
Android运行时ART加载类和方法的过程分析资料_第2页
Android运行时ART加载类和方法的过程分析资料_第3页
Android运行时ART加载类和方法的过程分析资料_第4页
Android运行时ART加载类和方法的过程分析资料_第5页
已阅读5页,还剩15页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

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类。在DEX文件中找到目标DEX方法的编号,并且以这个编号为索引,在上一步找到的OAT类中找到对应的OAT方法。使用上一步找到的OAT方法的成员变量begin_和code_offset_,计算出该方法对应的本地机器指令。通过前面一文的学习,我们可以知道,ART运行时的入口是类的静态成员函数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,andwillnotreturnuntiltheVMexits.*/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);......}}......}这个函数定义在文件在AndroidRuntimeJava虚拟机mJavaVM及其来的描述中,我们将不区分获得了 ART 虚拟机的

frameworks/base/core/jni/AndroidRuntime.cpp 中。类的成员函数 start中,首先是通过调用函数 startVm创建了一个JNI接口env。这个Java虚拟机实际上就是 ART运行时。在接下ART虚拟机和ART运行时,并且认为它们表达的是同一个概念。JNI 接口之后,就可以通过它提供的函数 FindClass 和GetStaticMethodID来加载最后就可以再通过

JNI 接口提供的函数

类及其静态成员函数CallStaticVoidMethod

main。于是,来调用类的静态成员函数main,以及进行到ART虚拟机里面去运行。接下来,我们就通过分析 JNI接口FindClass和GetStaticMethodID的实现,以便理解ART运行时是如何查找到指定的类和方法的。在接下来的一篇文章中,我们再分析运行时是如何通过 JNI接口CallStaticVoidMethod来执行指定类方法的本地机器指令的。

ART在分析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获得的是用来描述当前线程的主线程)的一个 Thread对象,再通过调用这个 Thread对象的成员函数一个JNI接口,并且保存在输出参数 p_env中。

(即ARTGetJniEnv

虚拟机就获得Thread

类的成员函数

GetJniEnv

的实现如下所示:[cpp]viewplaincopy在CODEclassPACKED(4)Thread{

上查看代码片派生到我的代码片public:......//JNImethodsJNIEnvExt*GetJniEnv()const{returnjni_env_;}......private:......EverythreadmayhaveanassociatedJNIenvironmentJNIEnvExt*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 的成员变量gJniNativeInterface。也就是说,JNI函数表实际是由全局变量

functions初始化为全局变量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<jclass>(c);}......};这个函数定义在文件 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<jobject>class_loader_object(soa.Env(),soa.AddLocalReference<jobject>(class_loader));std::stringclass_name_string(DescriptorToDot(descriptor));ScopedLocalRef<jobject>result(soa.Env(),NULL);{ScopedThreadStateChangetsc(self,kNative);ScopedLocalRef<jobject>class_name_object(soa.Env(),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-throwNPEtobecompatiblewithDalvikThrowNullPointerException(NULL,StringPrintf("ClassLoader.loadClassfor%s",

returned nullclass_name_string.c_str()).c_str());returnNULL;}else{//success,returnmirror::Class*returnsoa.Decode<mirror::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对象。通过调用这个类的成员函数 loadClass即可加载指定的类。在我们这个场景中,上述的类是一个系统类加载器,它负责加载系统类。而我们当前要加载的类为,它属于一个系统类。系统类加载器在加载系统类实际上也是通过 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

文件的格式,可以参考官方文档:

。知道了参数 descriptor指定的类定义在哪一个 DEX 文件之后,就可以通过ClassLinker类的另外一个成员函数 DefineClass来从中加载它了。接下来,我们就继续分析ClassLinker类的成员函数 DefineClass的实现,如下所示:[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片mirror::Class*ClassLinker::DefineClass(constchar*descriptor,mirror::ClassLoader*class_loader,constDexFile&dex_file,constDexFile::ClassDef&dex_class_def){Thread*self=Thread::Current();SirtRef<mirror::Class>klass(self,NULL);Loadtheclassfromthedexfile.if(UNLIKELY(!init_done_)){finishupinitofhandcraftedclass_roots_if(strcmp(descriptor,"Ljava/lang/Object;")==0){klass.reset(GetClassRoot(kJavaLangObject));}elseif(strcmp(descriptor,"Ljava/lang/Class;")==0){klass.reset(GetClassRoot(kJavaLangClass));}elseif(strcmp(descriptor,"Ljava/lang/String;")==0){klass.reset(GetClassRoot(kJavaLangString));}elseif(strcmp(descriptor,"Ljava/lang/DexCache;")==0){klass.reset(GetClassRoot(kJavaLangDexCache));}elseif(strcmp(descriptor,"Ljava/lang/reflect/ArtField;")==0){klass.reset(GetClassRoot(kJavaLangReflectArtField));}elseif(strcmp(descriptor,"Ljava/lang/reflect/ArtMethod;")==0){klass.reset(GetClassRoot(kJavaLangReflectArtMethod));}else{klass.reset(AllocClass(self,SizeOfClass(dex_file,dex_class_def)));}}else{klass.reset(AllocClass(self,SizeOfClass(dex_file,dex_class_def)));}......LoadClass(dex_file,dex_class_def,klass,class_loader);......{//Addthenewlyloadedclasstotheloadedclassestable.mirror::Class*existing=InsertClass(descriptor,klass.get(),Hash(descriptor));if(existing!=NULL){//Wefailedtoinsertbecauseweracedwithanotherthread.CallingEnsureResolvedmaycause//thisthreadtoblock.returnEnsureResolved(self,existing);}}......if(!LinkClass(klass,NULL,self)){//Linkingfailed.klass->SetStatus(mirror::Class::kStatusError,self);returnNULL;}......returnklass.get();}这个函数定义在文件

art/runtime/class_linker.cc

中。ClassLinker

类有一个类型为

bool

的成员变量

init_done_,用来表示

ClassLinker

是否已经初始化完成。 ClassLinker在创建的时候,有一个初始化过程,用来创建一些内部类。这些内部类要么是手动创建的,要么是从Image空间获得的。关于ART虚拟机的Image空间,我们在后面分析ART垃圾收集机制的文章中再详细分析。调用ClassLinker类的成员函数 DefineClass的时候,如果 ClassLinker正处于初始化过程,即其成员变量 init_done_的值等于 false,并且参数 descriptor描述的是特定的内部类,那么就将本地变量 klass指向它们,其余情况则会通过成员函数 AllocClass为其分配存储空间,以便后面通过成员函数 LoadClass进行初始化。ClassLinker类的成员函数 LoadClass用来从指定的 DEX文件中加载指定的类。指定的类从DEX文件中加载完成后,需要通过另外一个成员函数 InsertClass添加到ClassLinker的已加载类列表中去。 如果指定的类之前已经加载过, 即调用成员函数 InsertClass得到的返回值不等于空,那么就说明有另外的一个线程也正在加载指定的类。这时候就需要调用成员函数EnsureResolved来保证(等待)该类已经加载并且解析完成。另一方面,如果没有其它线程加载指定的类,那么当前线程从指定的DEX文件加载完成指定的类后,还需要调用成员函数LinkClass来对加载后的类进行解析。最后,一个类型为Class的对象就可以返回给调用者了,用来表示一个已经加载和解析完成的类。接下来,我们主要分析

ClassLinker

类的成员函数

LoadClass

的实现,以便可以了解类的加载过程。ClassLinker类的成员函数 LoadClass的实现如下所示:[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片voidClassLinker::LoadClass(constDexFile&dex_file,constDexFile::ClassDef&dex_class_def,SirtRef<mirror::Class>&klass,mirror::ClassLoader*class_loader){......klass->SetClassLoader(class_loader);......klass->SetDexClassDefIndex(dex_file.GetIndexForClassDef(dex_class_def));.....//Loadfieldsfields.constbyte*class_data=dex_file.GetClassData(dex_class_def);......ClassDataItemIteratorit(dex_file,class_data);Thread*self=Thread::Current();mirror::ObjectArray<mirror::ArtField>*

statics

=

AllocArtFieldArray(self,it.NumStaticFields());......klass->SetSFields(statics);}if(it.NumInstanceFields()!=0){mirror::ObjectArray<mirror::ArtField>*fields=AllocArtFieldArray(self,it.NumInstanceFields());......klass->SetIFields(fields);}for(size_ti=0;it.HasNextStaticField();i++,it.Next()){SirtRef<mirror::ArtField>sfield(self,AllocArtField(self));......klass->SetStaticField(i,sfield.get());LoadField(dex_file,it,klass,sfield);}for(size_ti=0;it.HasNextInstanceField();i++,it.Next()){SirtRef<mirror::ArtField>ifield(self,AllocArtField(self));......klass->SetInstanceField(i,ifield.get());LoadField(dex_file,it,klass,ifield);}UniquePtr<constOatFile::OatClass>oat_class;if(Runtime::Current()->IsStarted()&&!Runtime::Current()->UseCompileTimeClassPath()){oat_class.reset(GetOatClass(dex_file,klass->GetDexClassDefIndex()));}//Loadmethods.if(it.NumDirectMethods()!=0){//TODO:appenddirectmethodstoclassobjectmirror::ObjectArray<mirror::ArtMethod>*directs=AllocArtMethodArray(self,it.NumDirectMethods());......klass->SetDirectMethods(directs);}if(it.NumVirtualMethods()!=0){TODO:appenddirectmethodstoclassobjectmirror::ObjectArray<mirror::ArtMethod>*virtuals=AllocArtMethodArray(self,it.NumVirtualMethods());......klass->SetVirtualMethods(virtuals);}size_tclass_def_method_index=0;for(size_ti=0;it.HasNextDirectMethod();i++,it.Next()){SirtRef<mirror::ArtMethod>method(self,LoadMethod(self,dex_file,it,klass));......klass->SetDirectMethod(i,method.get());if(oat_class.get()!=NULL){LinkCode(method,oat_class.get(),class_def_method_index);}method->SetMethodIndex(class_def_method_index);class_def_method_index++;}for(size_ti=0;it.HasNextVirtualMethod();i++,it.Next()){SirtRef<mirror::ArtMethod>method(self,LoadMethod(self,dex_file,it,klass));......klass->SetVirtualMethod(i,method.get());......if(oat_class.get()!=NULL){LinkCode(method,oat_class.get(),class_def_method_index);}class_def_method_index++;}......}这个函数定义在文件 art/runtime/class_linker.cc中。我们首先要明确一下各个参数的含义:dex_file: 类型为DexFile,描述要加载的类所在的 DEX文件。dex_class_def:类型为ClassDef,描述要加载的类在 DEX文件里面的信息。klass:类型为Class,描述加载完成的类。class_loader: 类型为ClassLoader,描述所使用的类加载器。总的来说,ClassLinker类的成员函数LoadClassdex_class_def、class_loader三个参数包含的相关信息设置到参数以便可以得到一个完整的已加载类信息。

的任务就是要用dex_file、klass描述的Class对象去,ClassLinker类的成员函数 LoadClass主要完成的工作如下所示:将参数class_loader描述的ClassLoader设置到klass描述的Class对象中去,即给每一个已加载类关联一个类加载器。通过DexFile类的成员函数GetIndexForClassDef获得正在加载的类在DEX文件中的类索引号,并且设置到klass描述的Class对象中去。这个类索引号是一个很重要的信息,因为我们需要通过类索引号在相应的OAT文件找到一个OatClass结构体。有了这个OatClass结构体之后,我们才可以找到类方法对应的本地机器指令。具体可以参考前面图 1和一文。从参数dex_file描述的DEX文件中获得正在加载的类的静态成员变量和实例成员变量个数,并且为每一个静态成员变量和实例成员变量都分配一个 ArtField对象,接着通过ClassLinker类的成员函数LoadField对这些ArtField对象进行初始化。初始好得到的ArtField对象全部保存在 klass描述的Class对象中。4.调用ClassLinker类的成员函数 GetOatClass,从相应的 OAT文件中找到与正在加载的类对应的一个 OatClass结构体oat_class。这需要利用到上面提到的 DEX类索引号,这是因为DEX类和OAT类根据索引号存在一一对应关系。这一点可以参考图 1和一文。从参数dex_file描述的DEX文件中获得正在加载的类的直接成员函数和虚拟成员函数个数,并且为每一个直接成员函数和虚拟成员函数都分配一个 ArtMethod对象,接着通过ClassLinker类的成员函数LoadMethod对这些ArtMethod对象进行初始化。初始好得到的ArtMethod对象全部保存在klass描述的Class对象中。每一个直接成员函数和虚拟成员函数都对应有一个函数索引号。根据这个函数索引号可以在第4步得到的OatClass结构体中找到对应的本地机器指令,具体可以参考前面图1和一文。所有与这些成员函数关联的本地机器指令信息通过全局函数 LinkCode设置到klass描述的Class对象中。总结来说,参数 klass描述的Class对象包含了一系列的 ArtField对象和ArtMethod对象,其中,ArtField对象用来描述成员变量信息,而 ArtMethod用来描述成员函数信息。接下来,我们继续分析全局函数 LinkCode的实现,以便可以了解如何在一个 OAT文件中找到一个 DEX类方法的本地机器指令。函数LinkCode的实现如下所示:[cpp]viewplaincopy 在CODE上查看代码片派生到我的代码片staticvoidLinkCode(SirtRef<mirror::ArtMethod>&method,constOatFile::OatClass*oat_class,uint32_tmethod_index)SHARED_LOCKS_REQUIRED(Locks::mutator_lock_){//Methodshouldn'thavealreadybeenlinked.DCHECK(method->GetEntryPointFromCompiledCode()==NULL);Everykindofmethodshouldatleastgetaninvokestubfromtheoat_method.non-abstractmethodsalsogettheircodepointers.constOatFile::OatMethodoat_method=oat_class->GetOatMethod(method_index);oat_method.LinkMethod(method.get());Installentrypointfrominterpreter.Runtime*runtime=Runtime::Current();boolenter_interpreter=NeedsInterpreter(method.get(),method->GetEntryPointFromCompiledCode());if(enter_interpreter){method->SetEntryPointFromInterpreter(interpreter::artInterpreterToInterpreterBridge);}else{method->SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge);}if(method->IsAbstract()){method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());return;}if(method->IsStatic()&&!method->IsConstructor()){Forstaticmethodsexcludingtheclassinitializer,installthetrampoline.ItwillbereplacedbytheproperentrypointbyClassLinker::FixupStaticTrampolinesafterinitializingclass(seeClassLinker::InitializeClassmethod).method->SetEntryPointFromCompiledCode(GetResolutionTrampoline(runtime->GetClassLinker()));}elseif(enter_interpreter){Setentrypointfromcompiledcodeifthere'snocodeorininterpreteronlymode.method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());}if(method->IsNative()){Unregisteringrestoresthedlsymlookupstub.method->UnregisterNative(Thread::Current());}//Allowinstrumentationitschancetohijackcode.runtime->GetInstrumentation()->UpdateMethodsCode(method.get(),method->GetEntryPointFromCompiledCode());}这个函数定义在文件art/runtime/class_linker.cc中。参数method表示要设置本地机器指令的类方法,参数

oat_class表示类方法 method在OAT文件中对应的OatClass结构体,参数method_index表示类方法method的索引号。通过参数method_index描述的索引号可以在 oat_class表示的OatClass结构体中找到一个OatMethod结构体oat_method。这个OatMethod结构描述了类方法 method的本地机器指令相关信息,通过调用它的成员函数

LinkMethod

可以将这些信息设置到参数 method描述的ArtMethod对象中去。如下所示:[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片constvoid*OatFile::OatMethod::GetCode()const{returnGetOatPointer<constvoid*>(code_offset_);}......voidOatFile::OatMethod::LinkMethod(mirror::ArtMethod*method)const{CHECK(method!=NULL);method->SetEntryPointFromCompiledCode(GetCode());method->SetFrameSizeInBytes(frame_size_in_bytes_);method->SetCoreSpillMask(core_spill_mask_);method->SetFpSpillMask(fp_spill_mask_);method->SetMappingTable(GetMappingTable());method->SetVmapTable(GetVmapTable());method->SetNativeGcMap(GetNativeGcMap()); //UsedbynativemethodsinworkaroundJNImode.}中 的

这个函数定义在文件art/runtime/oat_file.cc中。其中,最重要的就是通过OatMethod类的成员函数字段,并且通过调用code_offset_

GetCode获得OatMethod结构体ArtMethod 类 的 成 员 函 数SetEntryPointFromCompiledCode设置到参数method描述的ArtMethod对象中去。OatMethod结构体中的code_offset_字段指向的是一个本地机器指令函数,这个本地机器指令函数正是通过翻译参数 method描述的类方法的 DEX字节码得到的。回到函数 LinkCode 中,它接着调用另外一个全局函数 NeedsInterpreter检查参数method描述的类方法是否需要通过解释器执行,它的实现如下所示:[cpp]viewplaincopy 在CODE上查看代码片派生到我的代码片//Returnstrueifthemethodmustrunwithinterpreter,falseotherwise.staticboolNeedsInterpreter(constmirror::ArtMethod*method,constvoid*code){if(code==NULL){//Nocode:needinterpreter.returntrue;}......Ifinterpretermodeisenabled,everymethod(exceptnativeandproxy)mustberunwithinterpreter.returnRuntime::Current()->GetInstrumentation()->InterpretOnly()&&!method->IsNative()&&!method->IsProxyMethod();}这个函数定义在文件 art/runtime/class_linker.cc中。在以下两种情况下,一个类方法需要通过解释器来执行:1.没有对应的本地机器指令,即参数 code的值等于 NULL。ART虚拟机运行在解释模式中,并且类方法不是JNI方法,并且也不是代理方法。调用Runtime类的静态成员函数 Current获得的是描述 ART运行时的一个 Runtime对象。调用这个 Runtime对象的成员函数 GetInstrumentation获得的是一个 Instrumentation对象。这个 Instrumentation 对象是用来调试 ART 运行时的,通过调用它的成员函数InterpretOnly可以知道 ART虚拟机是否运行在解释模式中。中,JNI

因为JNI方法是没有对应的 DEX字节码的,因此即使 ART虚拟机运行在解释模式方法也不能通过解释器来执行。至于代理方法,由于是动态生成的(没有对应的DEX

字节码),因此即使

ART

虚拟机运行在解释模式中,它们也不通过解释器来执行(这一点猜测的,还没有确认)

。回到函数 LinkCode中,如果调用函数 NeedsInterpreter得到的返回值 enter_interpreter等于true,那么就意味着参数 method描述的类方法需要通过解释器来执行,这时候就将函数artInterpreterToInterpreterBridge设置为解释器执行该类方法的入口点。否则的话,就将另外一个函数artInterpreterToCompiledCodeBridge设置为解释器执行该类方法的入口点。为什么我们需要为类方法设置解释器入口点呢?根据前面的分析可以知道, 在ART虚拟机中,并不是所有的类方法都是有对应的本地机器指令的, 并且即使一个类方法有对应的本地机器指令,当 ART虚拟机以解释模式运行时,它也需要通过解释器来执行。当以解释器执行的类方法在执行的过程中调用了其它的类方法时, 解释器就需要进一步知道被调用的类方法是应用以解释方式执行, 还是本地机器指令方法执行。 为了能够进行统一处理, 就给每一个类方法都设置一个解释器入口点。 需要通过解释执行的类方法的解释器入口点函数是artInterpreterToInterpreterBridge,它会继续通过解释器来执行该类方法。需要通过本地机器指令执行的类方法的解释器入口点函数是artInterpreterToCompiledCodeBridge,它会间接地调用该类方法的本地机器指令。函数LinkCode继续往下执行,判断参数method描述的类方法是否是一个抽象方法。抽象方法声明类中是没有实现的,必须要由子类实现。因此抽象方法在声明类中是没有对应的本地机器指令的,它们必须要通过解释器来执行。不过,为了能够进行统一处理,我们仍然假装抽象方法有对应的本地机器指令函数,只不过这个本地机器指令函数被设置为GetCompiledCodeToInterpreterBridge。当函数GetCompiledCodeToInterpreterBridge被调用时,就会自动进入到解释器中去。对于非抽象方法,函数 LinkCode还要继续往下处理。到这里有一点是需要注意的,前面通过调用 OatMethod类的成员函数 LinkMethod,我们已经设置好参数 method描述的类方法的本地机器指令了。但是,在以下两种情况下,我们需要进行调整:1.当参数method描述的类方法是一个非类静态初始化函数(classinitializer)的静态方法时,我们不能直接执行翻译其DEX字节码得到的本地机器指令。这是因为类静态方法可以在不创建类对象的前提下执行。这意味着一个类静态方法在执行的时候,对应的类可能还没有初始化好。这时候我们就需要先将对应的类初始化好,再执行相应的静态方法。为了能够做到这一点。我们就先调用GetResolutionTrampoline函数得到一个Tampoline函数,接着将这个Trampoline函数作为静态方法的本地机器指令。这样如果类静态方法在对应的类初始化前被调用,就会触发上述的Trampoline函数被执行。而当上述Trampoline函数执行时,它们先初始化好对应的类,再调用原来的类静态方法对应的本地机器指令。按照代码中的注释,当一个类初始化完成之后,就可以调用函数ClassLinker::FixupStaticTrampolines来修复该类的静态成员函数的本地机器指令,也是通过翻译DEX字节码得到的本地机器指令。这里需要注意的是,为什么类静态初始化函数不需要按照其它的类静态方法一样设置Tampoline函数呢?这是因为类静态初始化函数是一定保证是在类初始化过程中执行的。当参数method描述的类方法需要通过解释器执行时,那么当该类方法执行时,就不能执行它的本地机器指令,因此我们就先调用GetCompiledCodeToInterpreterBridge函数获得一个桥接函数,并且将这个桥接函数假装为类方法的本地机器指令。一旦该桥接函数被执行,它就会入到解释器去执行类方法。通过这种方式,我们就可以以统一的方法来调用解释执行和本地机器指令执行的类方法。函数LinkCode接下来继续判断参数 method描述的类方法是否是一个 JNI方法。如果是的话,那么就调用 ArtMethod 类的成员函数 UnregisterNative来初始化它的 JNI方法调用接口。ArtMethod类的成员函数 UnregisterNative的实现如下所示:[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片voidArtMethod::UnregisterNative(Thread*self){CHECK(IsNative())<<PrettyMethod(this);restorestubtolookupnativepointerviadlsymRegisterNative(self,GetJniDlsymLookupStub());}这个函数定义在文件 runtime/mirror/art_method.cc 中。ArtMethod类的成员函数 UnregisterNative实际上就是将一个 JNI方法的初始化入口设置为通过调用函数 GetJniDlsymLookupStub 获得的一个 Stub。这个 Stub的作用是,当一个JNI方法被调用时,如果还没有显示地注册有 Native函数,那么它就会自动从已加载的SO文件查找是否存在一个对应的 Native函数。如果存在的话,就将它注册为 JNI方法的Native函数,并且执行它。这就是隐式的 JNI方法注册。回到函数 LinkCode,它最后调用 Instrumentation类的成员函数 UpdateMethodsCode检查是否要进一步修改参数 method描述的类方法的本地机器指令入口, 它的实现如下所示:[cpp]viewplaincopy 在CODE上查看代码片派生到我的代码片voidInstrumentation::UpdateMethodsCode(mirror::ArtMethod*method,constvoid*code)const{if(LIKELY(!instrumentation_stubs_installed_)){method->SetEntryPointFromCompiledCode(code);}else{if(!interpreter_stubs_installed_||method->IsNative()){method->SetEntryPointFromCompiledCode(GetQuickInstrumentationEntryPoint());}else{method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());}}}这个函数定义在文件 art/runtime/instrumentation.cc 中。Instrumentation类是用来调用 ART运行时的。例如,当我们需要监控类方法的调用时,就可以往 Instrumentation注册一些 Listener。这样当类方法调用时,这些注册的 Listener就会得到回调。当 Instrumentation 注册有相应的 Listener 时,它的成员变量instrumentation_stubs_installed_的值就会等于

true。因此,当Instrumentation类的成员变量 instrumentation_stubs_installed_的值等于 true时,我们需要使用一个监控函数来替换掉类方法原来的本地机器指令。 这样当类方法被调用时,监控函数就获得控制权,它可以在调用原来的本地机器指令前后,向注册的 Listener发出通知。对于JNI方法,我们通过调用函数 GetQuickInstrumentationEntryPoint 获得的函数作为其监控函数;而对其它的类方法,我们通过调用函数 GetCompiledCodeToInterpreterBridge获得的函数作为其监控函数。另一方面,如果没有 Listener 注册到 Instrumentation 中,即它的成员变量instrumentation_stubs_installed_的值等于 false,那么 Instrumentation 类的成员函UpdateMethodsCode就会使用参数 code描述的本地机器指令作为参数 method描述的类方法的本地机器指令入口。 参数code描述的本地机器指一般就是翻译类方法的 DEX字节码得到的本地机器指令了。实际上是相当于没有修改类方法的本地机器指令入口。这样,一个类的加载过程就完成了。加载完成后,得到的是一个 Class对象。这个Class对象关联有一系列的 ArtField对象和ArtMethod对象。其中,ArtField对象描述的是成员变量,而ArtMethod对象描述的是成员函数。对于每一个ArtMethod对象,它都有一个解释器入口点和一个本地机器指令入口点。这样,无论一个类方法是通过解释器执行,还是直接以本地机器指令执行,我们都可以以统一的方式来进行调用。同时,理解了上述的类加载过程后,我们就可以知道,我们在Native层通过JNI接口FindClass查找或者加载类时,得到的一个不透明的jclass值,实际上

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论