1、Android运行时ART加载类和方法的过程分析Android运行时ART加载类和方法的过程分析在前一篇文章中,我们通过分析OAT文件的加载过程,认识了OAT文件的格式,其中包含了原始的DEX文件。既然ART运行时执行的都是翻译DEX字节码后得到的本地机器指令了,为什么还需要在OAT文件中包含DEX文件,并且将它加载到内存去呢?这是因为ART运行时提供了Java虚拟机接口,而要实现Java虚拟机接口不得不依赖于DEX文件。本文就通过分析ART运行时加载类及其方法的过程来理解DEX文件的作用。在前面这篇文章的最后,我们简单总结了ART运行时查找类方法的本地机器指令的过程,如图1所示:为了方便描述
2、,我们将DEX文件中描述的类和方法称为DEX类(Dex Class)和DEX方法(Dex Method),而将在OAT文件中描述的类和方法称为OAT类(Oat Class)和OAT方法(Oat Method)。接下来我们还会看到,ART运行时在内部又会使用另外两个不同的术语来描述类和方法,其中将类描述为Class,而将类方法描述为ArtMethod。 在图1中,为了找到一个类方法的本地机器指令,我们需要执行以下的操作: 1. 在DEX文件中找到目标DEX类的编号,并且以这个编号为索引,在OAT文件中找到对应的OAT类。 2. 在DEX文件中找到目标DEX方法的编号,并且以这个编号为索引,在上一
3、步找到的OAT类中找到对应的OAT方法。 3. 使用上一步找到的OAT方法的成员变量begin_和code_offset_,计算出该方法对应的本地机器指令。 通过前面一文的学习,我们可以知道,ART运行时的入口是com.Android.internal.os.ZygoteInit类的静态成员函数main,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void AndroidRuntime:start(const char* className, const char* options) . /* start the virtual machine */
4、 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. */ char* slashClassName = toSlashClassName(className); jclass startClass = env-Fi
5、ndClass(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 (startMeth = NULL) ALOGE(JavaVM unable to find main() in %sn, className); /* keep g
6、oing */ 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接口之后,就可以通过它提供的函数
7、FindClass和GetStaticMethodID来加载com.android.internal.os.ZygoteInit类及其静态成员函数main。于是,最后就可以再通过JNI接口提供的函数CallStaticVoidMethod来调用com.android.internal.os.ZygoteInit类的静态成员函数main,以及进行到ART虚拟机里面去运行。 接下来,我们就通过分析JNI接口FindClass和GetStaticMethodID的实现,以便理解ART运行时是如何查找到指定的类和方法的。在接下来的一篇文章中,我们再分析ART运行时是如何通过JNI接口CallStati
8、cVoidMethod来执行指定类方法的本地机器指令的。 在分析JNI接口FindClass和GetStaticMethodID的实现之前,我们先要讲清楚JNI接口是如何创建的。从前面一文可以知道,与ART虚拟机主线程关联的JNI接口是在函数JNI_CreateJavaVM中创建的,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片extern C jint JNI_CreateJavaVM(JavaVM* p_vm, JNIEnv* p_env, void* vm_args) . *p_env = Thread:Current()-GetJniEnv()
9、; . return JNI_OK; 这个函数定义在文件art/runtime/jni_internal.cc中。 调用Thread类的静态成员函数Current获得的是用来描述当前线程(即ART虚拟机的主线程)的一个Thread对象,再通过调用这个Thread对象的成员函数GetJniEnv就获得一个JNI接口,并且保存在输出参数p_env中。 Thread类的成员函数GetJniEnv的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片class PACKED(4) Thread public: . / JNI methods JNIEnvExt*
10、 GetJniEnv() const return jni_env_; . private: . / Every thread may have an associated JNI environment JNIEnvExt* jni_env_; . ; 这个函数定义在文件art/runtime/thread.h中。 Thread类的成员函数GetJniEnv返回的是成员变量jni_env_指向的一个JNIEnvExt对象。 JNIEnvExt类是从JNIEnv类继承下来的,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片struct JNIEnvExt
11、 : public JNIEnv . ; 这个类定义在文件art/runtime/jni_internal.h。 JNIEnv类定义了JNI接口,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片typedef _JNIEnv JNIEnv; . struct _JNIEnv /* do not rename this; it does not seem to be entirely opaque */ const struct JNINativeInterface* functions; . jint GetVersion() return functi
12、ons-GetVersion(this); . ; 这个类定义在文件libnativehelper/include/nativehelper/jni.h中。 在JNIEnv类中,最重要的就是成员变量functions了,它指向的是一个类型为JNINativeInterface的JNI函数表。所有的JNI接口调用都是通过这个JNI函数表来实现的。例如,用来获得版本号的JNI接口GetVersion就是通过调用JNI函数表中的GetVersion函数来实现的。 那么,上述的JNI函数表是如何创建的呢?通过JNIEnvExt类的构造函数可以知道答案,如下所示:cpp view plain copy
13、在CODE上查看代码片派生到我的代码片JNIEnvExt:JNIEnvExt(Thread* self, JavaVMExt* vm) : . functions = unchecked_functions = &gJniNativeInterface; . 这个函数定义在文件art/runtime/jni_internal.cc中。 JNIEnvExt类的构造函数将父类JNIEnv的成员变量functions初始化为全局变量gJniNativeInterface。也就是说,JNI函数表实际是由全局变量gJniNativeInterface来描述的。 全局变量gJniNativeInterfa
14、ce的定义如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片const JNINativeInterface gJniNativeInterface = NULL, / reserved0. NULL, / reserved1. NULL, / reserved2. NULL, / reserved3. JNI:GetVersion, . JNI:FindClass, . JNI:GetStaticMethodID, . JNI:CallStaticVoidMethod, . ; 这个全局变量定义在文件art/runtime/jni_internal.cc
15、中。 从这里可以看出,JNI函数表实际上是由JNI类的静态成员函数组成的。例如,JNI函数GetVersion是由JNI类的静态成员函数GetVersion来实现的。理解了这一点之后,我们就轻松地知道同接下来我们要分析的JNI接口FindClass和GetStaticMethodID分别是由JNI类的静态成员函数FindClass和GetStaticMethodID来实现的。事实上,如果读者看过这篇文章,那么对上述的JNI接口定义是一目了然的。 JNI类的静态成员函数FindClass的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片class JN
16、I public: . static jclass FindClass(JNIEnv* env, const char* name) CHECK_NON_NULL_ARGUMENT(FindClass, name); Runtime* runtime = Runtime:Current(); ClassLinker* class_linker = runtime-GetClassLinker(); std:string descriptor(NormalizeJniClassDescriptor(name); ScopedObjectAccess soa(env); Class* c = NU
17、LL; if (runtime-IsStarted() ClassLoader* cl = GetClassLoader(soa); c = class_linker-FindClass(descriptor.c_str(), cl); else c = class_linker-FindSystemClass(descriptor.c_str(); return soa.AddLocalReference(c); . ; 这个函数定义在文件art/runtime/jni_internal.cc中。 在ART虚拟机进程中,存在着一个Runtime单例,用来描述ART运行时。通过调用Runtim
18、e类的静态成员函数Current可以获得上述Runtime单例。获得了这个单例之后,就可以调用它的成员函数GetClassLinker来获得一个ClassLinker对象。从前面一文可以知道。上述ClassLinker对象是在创建ART虚拟机的过程中创建的,用来加载类以及链接类方法。 JNI类的静态成员函数FindClass首先是判断ART运行时是否已经启动起来。如果已经启动,那么就通过调用函数GetClassLoader来获得当前线程所关联的ClassLoader,并且以此为参数,调用前面获得的ClassLinker对象的成员函数FindClass来加载由参数name指定的类。一般来说,当前
19、线程所关联的ClassLoader就是当前正在执行的类方法所关联的ClassLoader,即用来加载当前正在执行的类的ClassLoader。如果ART虚拟机还没有开始执行类方法,就像我们现在这个场景,那么当前线程所关联的ClassLoader实际上就系统类加载器,即SystemClassLoader。 如果ART运行时还没有启动,那么这时候只可以加载系统类。这个通过前面获得的ClassLinker对象的成员函数FindSystemClass来实现的。在我们这个场景中,ART运行时已经启动,因此,接下来我们就继续分析ClassLinker类的成员函数FindClass的实现。 ClassLin
20、ker类的成员函数FindClass的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片mirror:Class* ClassLinker:FindClass(const char* descriptor, mirror:ClassLoader* class_loader) . Thread* self = Thread:Current(); . / Find the class in the loaded classes table. mirror:Class* klass = LookupClass(descriptor, class_loader
21、); if (klass != NULL) return EnsureResolved(self, klass); / Class is not yet loaded. if (descriptor0 = ) . else if (class_loader = NULL) DexFile:ClassPathEntry pair = DexFile:FindInClassPath(descriptor, boot_class_path_); if (pair.second != NULL) return DefineClass(descriptor, NULL, *pair.first, *pa
22、ir.second); else if (Runtime:Current()-UseCompileTimeClassPath() . else ScopedObjectAccessUnchecked soa(self-GetJniEnv(); ScopedLocalRef class_loader_object(soa.Env(), soa.AddLocalReference(class_loader); std:string class_name_string(DescriptorToDot(descriptor); ScopedLocalRef result(soa.Env(), NULL
23、); ScopedThreadStateChange tsc(self, kNative); ScopedLocalRef class_name_object(soa.Env(), soa.Env()-NewStringUTF(class_name_string.c_str(); if (class_name_object.get() = NULL) return NULL; CHECK(class_loader_object.get() != NULL); result.reset(soa.Env()-CallObjectMethod(class_loader_object.get(), W
24、ellKnownClasses:java_lang_ClassLoader_loadClass, class_name_object.get(); if (soa.Self()-IsExceptionPending() / If the ClassLoader threw, pass that exception up. return NULL; else if (result.get() = NULL) / broken loader - throw NPE to be compatible with Dalvik ThrowNullPointerException(NULL, String
25、Printf(ClassLoader.loadClass returned null for %s, class_name_string.c_str().c_str(); return NULL; else / success, return mirror:Class* return soa.Decode(result.get(); ThrowNoClassDefFoundError(Class %s not found, PrintableString(descriptor).c_str(); return NULL; 这个函数定义在文件art/runtime/class_linker.cc
26、中。 参数descriptor指向的是要加载的类的签名,而参数class_loader指向的是一个类加载器,我们假设它的值不为空,并且指向系统类加载器。 ClassLinker类的成员函数FindClass首先是调用另外一个成员函数LookupClass来检查参数descriptor指定的类是否已经被加载过。如果是的话,那么ClassLinker类的成员函数LookupClass就会返回一个对应的Class对象,这个Class对象接着就会返回给调用者,表示加载已经完成。 如果参数descriptor指定的类还没有被加载过,这时候主要就是要看参数class_loader的值了。如果参数class
27、_loader的值等于NULL,那么就需要调用DexFile类的静态FindInClassPath来在系统启动类路径寻找对应的类。一旦寻找到,那么就会获得包含目标类的DEX文件,因此接下来就调用ClassLinker类的另外一个成员函数DefineClass从获得的DEX文件中加载参数descriptor指定的类了。 如果参数class_loader的值不等于NULL,也就是说ClassLinker类的成员函数FindClass的调用者指定了类加载器,那么就通过该类加载器来加载参数descriptor指定的类。每一个类加载器在Java层都对应有一个java.lang.ClassLoader对象
28、。通过调用这个java.lang.ClassLoader类的成员函数loadClass即可加载指定的类。在我们这个场景中,上述的java.lang.ClassLoader类是一个系统类加载器,它负责加载系统类。而我们当前要加载的类为com.android.internal.os.ZygoteInit,它属于一个系统类。 系统类加载器在加载系统类实际上也是通过JNI方法调用ClassLinker类的成员函数FindClass来实现的。只不过这时候传进来的参数class_loader是一个NULL值。这样,ClassLinker类的成员函数FindClass就会在系统启动类路径中寻找参数descr
29、iptor指定的类可以在哪一个DEX文件加载,这是通过调用DexFile类的静态成员函数FindInClassPath来实现的。 所谓的系统启动类路径,其实就是一系列指定的由系统提供的DEX文件,这些DEX文件保存在ClassLinker类的成员变量boot_class_path_描述的一个向量中。那么问题就来了,这些DEX文件是怎么来的呢?我们知道,在ART运行时中,我们使用的是OAT文件。如果看过前面这篇文章,就会很容易知道,OAT文件里面包含有DEX文件。而且ART运行时在启动的时候,会加载一个名称为systemframeworkboot.artclasses.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