Android运行时ART加载类和方法的过程分析.docx

上传人:b****3 文档编号:3485476 上传时间:2022-11-23 格式:DOCX 页数:25 大小:69.86KB
下载 相关 举报
Android运行时ART加载类和方法的过程分析.docx_第1页
第1页 / 共25页
Android运行时ART加载类和方法的过程分析.docx_第2页
第2页 / 共25页
Android运行时ART加载类和方法的过程分析.docx_第3页
第3页 / 共25页
Android运行时ART加载类和方法的过程分析.docx_第4页
第4页 / 共25页
Android运行时ART加载类和方法的过程分析.docx_第5页
第5页 / 共25页
点击查看更多>>
下载资源
资源描述

Android运行时ART加载类和方法的过程分析.docx

《Android运行时ART加载类和方法的过程分析.docx》由会员分享,可在线阅读,更多相关《Android运行时ART加载类和方法的过程分析.docx(25页珍藏版)》请在冰豆网上搜索。

Android运行时ART加载类和方法的过程分析.docx

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(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());

ScopedLocalRefclass_loader_object(soa.Env(),

soa.AddLocalReference(class_loader));

std:

:

stringclass_name_string(DescriptorToDot(descriptor));

ScopedLocalRefresult(soa.Env(),NULL);

{

ScopedThreadStateChangetsc(self,kNative);

ScopedLocalRefclass_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-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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 其它课程

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1