首页 > 代码库 > Android执行时ART载入类和方法的过程分析
Android执行时ART载入类和方法的过程分析
在前一篇文章中。我们通过分析OAT文件的载入过程,认识了OAT文件的格式,当中包括了原始的DEX文件。
既然ART运行时运行的都是翻译DEX字节码后得到的本地机器指令了。为什么还须要在OAT文件里包括DEX文件,而且将它载入到内存去呢?这是由于ART运行时提供了Java虚拟机接口,而要实现Java虚拟机接口不得不依赖于DEX文件。
本文就通过分析ART运行时载入类及其方法的过程来理解DEX文件的作用。
老罗的新浪微博:http://weibo.com/shengyangluo。欢迎关注!
《Android系统源码情景分析》一书正在进击的程序猿网(http://0xcc0xcd.com)中连载,点击进入!
在前面Android运行时ART载入OAT文件的过程分析这篇文章的最后。我们简单总结了ART运行时查找类方法的本地机器指令的过程,如图1所看到的:
图1 ART运行时查找类方法的本地机器指令的过程
为了方便描写叙述,我们将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方法的编号。而且以这个编号为索引,在上一步找到的OAT类中找到相应的OAT方法。
3. 使用上一步找到的OAT方法的成员变量begin_和code_offset_,计算出该方法相应的本地机器指令。
通过前面Android运行时ART简要介绍和学习计划一文的学习。我们能够知道,ART运行时的入口是com.android.internal.os.ZygoteInit类的静态成员函数main。例如以下所看到的:
void AndroidRuntime::start(const char* className, const char* options) { ...... /* start the virtual machine */ 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->FindClass(slashClassName); if (startClass == NULL) { ALOGE("JavaVM unable to locate class ‘%s‘\n", slashClassName); /* keep going */ } else { jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V"); if (startMeth == NULL) { ALOGE("JavaVM unable to find main() in ‘%s‘\n", className); /* keep going */ } 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接口是怎样创建的。从前面Android运行时ART载入OAT文件的过程分析一文能够知道,与ART虚拟机主线程关联的JNI接口是在函数JNI_CreateJavaVM中创建的,例如以下所看到的:
extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { ...... *p_env = Thread::Current()->GetJniEnv(); ...... return JNI_OK; }这个函数定义在文件art/runtime/jni_internal.cc中。
调用Thread类的静态成员函数Current获得的是用来描写叙述当前线程(即ART虚拟机的主线程)的一个Thread对象,再通过调用这个Thread对象的成员函数GetJniEnv就获得一个JNI接口,而且保存在输出參数p_env中。
Thread类的成员函数GetJniEnv的实现例如以下所看到的:
class PACKED(4) Thread { public: ...... // JNI methods JNIEnvExt* 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类继承下来的。例如以下所看到的:
struct JNIEnvExt : public JNIEnv { ...... };这个类定义在文件art/runtime/jni_internal.h。
JNIEnv类定义了JNI接口,例如以下所看到的:
typedef _JNIEnv JNIEnv; ...... struct _JNIEnv { /* do not rename this; it does not seem to be entirely opaque */ const struct JNINativeInterface* functions; ...... jint GetVersion() { return functions->GetVersion(this); } ...... };这个类定义在文件libnativehelper/include/nativehelper/jni.h中。
在JNIEnv类中,最重要的就是成员变量functions了,它指向的是一个类型为JNINativeInterface的JNI函数表。全部的JNI接口调用都是通过这个JNI函数表来实现的。比如,用来获得版本的JNI接口GetVersion就是通过调用JNI函数表中的GetVersion函数来实现的。
那么。上述的JNI函数表是怎样创建的呢?通过JNIEnvExt类的构造函数能够知道答案。例如以下所看到的:
JNIEnvExt::JNIEnvExt(Thread* self, JavaVMExt* vm) : ...... { functions = unchecked_functions = &gJniNativeInterface; ...... }这个函数定义在文件art/runtime/jni_internal.cc中。
JNIEnvExt类的构造函数将父类JNIEnv的成员变量functions初始化为全局变量gJniNativeInterface。
也就是说。JNI函数表实际是由全局变量gJniNativeInterface来描写叙述的。
全局变量gJniNativeInterface的定义例如以下所看到的:
const JNINativeInterface gJniNativeInterface = { 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来实现的。其实,假设读者看过Dalvik虚拟机的启动过程分析这篇文章,那么对上述的JNI接口定义是一目了然的。
JNI类的静态成员函数FindClass的实现例如以下所看到的:
class JNI { 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 = NULL; 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<jclass>(c); } ...... };这个函数定义在文件art/runtime/jni_internal.cc中。
在ART虚拟机进程中。存在着一个Runtime单例,用来描写叙述ART运行时。
通过调用Runtime类的静态成员函数Current能够获得上述Runtime单例。
获得了这个单例之后,就能够调用它的成员函数GetClassLinker来获得一个ClassLinker对象。从前面Android运行时ART载入OAT文件的过程分析一文能够知道。
上述ClassLinker对象是在创建ART虚拟机的过程中创建的。用来载入类以及链接类方法。
JNI类的静态成员函数FindClass首先是推断ART运行时是否已经启动起来。假设已经启动,那么就通过调用函数GetClassLoader来获得当前线程所关联的ClassLoader,而且以此为參数。调用前面获得的ClassLinker对象的成员函数FindClass来载入由參数name指定的类。一般来说,当前线程所关联的ClassLoader就是当前正在运行的类方法所关联的ClassLoader。即用来载入当前正在运行的类的ClassLoader。假设ART虚拟机还没有開始运行类方法,就像我们如今这个场景,那么当前线程所关联的ClassLoader实际上就系统类载入器。即SystemClassLoader。
假设ART运行时还没有启动。那么这时候仅仅能够载入系统类。这个通过前面获得的ClassLinker对象的成员函数FindSystemClass来实现的。
在我们这个场景中。ART运行时已经启动。因此,接下来我们就继续分析ClassLinker类的成员函数FindClass的实现。
ClassLinker类的成员函数FindClass的实现例如以下所看到的:
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); if (klass != NULL) { return EnsureResolved(self, klass); } // Class is not yet loaded. if (descriptor[0] == ‘[‘) { ...... } else if (class_loader == NULL) { DexFile::ClassPathEntry pair = DexFile::FindInClassPath(descriptor, boot_class_path_); if (pair.second != NULL) { return DefineClass(descriptor, NULL, *pair.first, *pair.second); } } else if (Runtime::Current()->UseCompileTimeClassPath()) { ...... } else { ScopedObjectAccessUnchecked soa(self->GetJniEnv()); ScopedLocalRef<jobject> class_loader_object(soa.Env(), soa.AddLocalReference<jobject>(class_loader)); std::string class_name_string(DescriptorToDot(descriptor)); ScopedLocalRef<jobject> result(soa.Env(), NULL); { ScopedThreadStateChange tsc(self, kNative); ScopedLocalRef<jobject> 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(), WellKnownClasses::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, StringPrintf("ClassLoader.loadClass returned null for %s", class_name_string.c_str()).c_str()); return NULL; } else { // success, return mirror::Class* return soa.Decode<mirror::Class*>(result.get()); } } ThrowNoClassDefFoundError("Class %s not found", PrintableString(descriptor).c_str()); return NULL; }这个函数定义在文件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文件。假设看过前面Android运行时ART载入OAT文件的过程分析这篇文章。就会非常easy知道。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文件的格式,能够參考官方文档:http://source.android.com/tech/dalvik/index.html。
知道了參数descriptor指定的类定义在哪一个DEX文件之后。就能够通过ClassLinker类的另外一个成员函数DefineClass来从中载入它了。
接下来,我们就继续分析ClassLinker类的成员函数DefineClass的实现,例如以下所看到的:
mirror::Class* ClassLinker::DefineClass(const char* descriptor, mirror::ClassLoader* class_loader, const DexFile& dex_file, const DexFile::ClassDef& dex_class_def) { Thread* self = Thread::Current(); SirtRef<mirror::Class> klass(self, NULL); // Load the class from the dex file. if (UNLIKELY(!init_done_)) { // finish up init of hand crafted class_roots_ if (strcmp(descriptor, "Ljava/lang/Object;") == 0) { klass.reset(GetClassRoot(kJavaLangObject)); } else if (strcmp(descriptor, "Ljava/lang/Class;") == 0) { klass.reset(GetClassRoot(kJavaLangClass)); } else if (strcmp(descriptor, "Ljava/lang/String;") == 0) { klass.reset(GetClassRoot(kJavaLangString)); } else if (strcmp(descriptor, "Ljava/lang/DexCache;") == 0) { klass.reset(GetClassRoot(kJavaLangDexCache)); } else if (strcmp(descriptor, "Ljava/lang/reflect/ArtField;") == 0) { klass.reset(GetClassRoot(kJavaLangReflectArtField)); } else if (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); ...... { // Add the newly loaded class to the loaded classes table. mirror::Class* existing = InsertClass(descriptor, klass.get(), Hash(descriptor)); if (existing != NULL) { // We failed to insert because we raced with another thread. Calling EnsureResolved may cause // this thread to block. return EnsureResolved(self, existing); } } ...... if (!LinkClass(klass, NULL, self)) { // Linking failed. klass->SetStatus(mirror::Class::kStatusError, self); return NULL; } ...... return klass.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的实现例如以下所看到的:
void ClassLinker::LoadClass(const DexFile& dex_file, const DexFile::ClassDef& dex_class_def, SirtRef<mirror::Class>& klass, mirror::ClassLoader* class_loader) { ...... klass->SetClassLoader(class_loader); ...... klass->SetDexClassDefIndex(dex_file.GetIndexForClassDef(dex_class_def)); ..... // Load fields fields. const byte* class_data = http://www.mamicode.com/dex_file.GetClassData(dex_class_def);> 这个函数定义在文件art/runtime/class_linker.cc中。我们首先要明白一下各个參数的含义:
dex_file: 类型为DexFile,描写叙述要载入的类所在的DEX文件。
dex_class_def: 类型为ClassDef。描写叙述要载入的类在DEX文件里面的信息。
klass: 类型为Class,描写叙述载入完毕的类。
class_loader: 类型为ClassLoader,描写叙述所使用的类载入器。
总的来说,ClassLinker类的成员函数LoadClass的任务就是要用dex_file、dex_class_def、class_loader三个參数包括的相关信息设置到參数klass描写叙述的Class对象去,以便能够得到一个完整的已载入类信息。
ClassLinker类的成员函数LoadClass主要完毕的工作例如以下所看到的:
1. 将參数class_loader描写叙述的ClassLoader设置到klass描写叙述的Class对象中去,即给每个已载入类关联一个类载入器。
2. 通过DexFile类的成员函数GetIndexForClassDef获得正在载入的类在DEX文件里的类索引號,而且设置到klass描写叙述的Class对象中去。这个类索引號是一个非常重要的信息,由于我们须要通过类索引號在相应的OAT文件找到一个OatClass结构体。有了这个OatClass结构体之后,我们才干够找到类方法相应的本地机器指令。详细能够參考前面图1和Android运行时ART载入OAT文件的过程分析一文。
3. 从參数dex_file描写叙述的DEX文件里获得正在载入的类的静态成员变量和实例成员变量个数,而且为每个静态成员变量和实例成员变量都分配一个ArtField对象,接着通过ClassLinker类的成员函数LoadField对这些ArtField对象进行初始化。
初始好得到的ArtField对象全部保存在klass描写叙述的Class对象中。
4. 调用ClassLinker类的成员函数GetOatClass,从相应的OAT文件里找到与正在载入的类相应的一个OatClass结构体oat_class。这须要利用到上面提到的DEX类索引號,这是由于DEX类和OAT类依据索引號存在一一相应关系。
这一点能够參考图1和Android运行时ART载入OAT文件的过程分析一文。
5. 从參数dex_file描写叙述的DEX文件里获得正在载入的类的直接成员函数和虚拟成员函数个数,而且为每个直接成员函数和虚拟成员函数都分配一个ArtMethod对象。接着通过ClassLinker类的成员函数LoadMethod对这些ArtMethod对象进行初始化。初始好得到的ArtMethod对象全部保存在klass描写叙述的Class对象中。
6. 每个直接成员函数和虚拟成员函数都相应有一个函数索引號。依据这个函数索引號能够在第4步得到的OatClass结构体中找到相应的本地机器指令,详细能够參考前面图1和Android运行时ART载入OAT文件的过程分析一文。全部与这些成员函数关联的本地机器指令信息通过全局函数LinkCode设置到klass描写叙述的Class对象中。
总结来说,參数klass描写叙述的Class对象包括了一系列的ArtField对象和ArtMethod对象,当中,ArtField对象用来描写叙述成员变量信息,而ArtMethod用来描写叙述成员函数信息。
接下来。我们继续分析全局函数LinkCode的实现,以便能够了解怎样在一个OAT文件里找到一个DEX类方法的本地机器指令。
函数LinkCode的实现例如以下所看到的:
static void LinkCode(SirtRef<mirror::ArtMethod>& method, const OatFile::OatClass* oat_class, uint32_t method_index) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { // Method shouldn‘t have already been linked. DCHECK(method->GetEntryPointFromCompiledCode() == NULL); // Every kind of method should at least get an invoke stub from the oat_method. // non-abstract methods also get their code pointers. const OatFile::OatMethod oat_method = oat_class->GetOatMethod(method_index); oat_method.LinkMethod(method.get()); // Install entry point from interpreter. Runtime* runtime = Runtime::Current(); bool enter_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()) { // For static methods excluding the class initializer, install the trampoline. // It will be replaced by the proper entry point by ClassLinker::FixupStaticTrampolines // after initializing class (see ClassLinker::InitializeClass method). method->SetEntryPointFromCompiledCode(GetResolutionTrampoline(runtime->GetClassLinker())); } else if (enter_interpreter) { // Set entry point from compiled code if there‘s no code or in interpreter only mode. method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge()); } if (method->IsNative()) { // Unregistering restores the dlsym lookup stub. method->UnregisterNative(Thread::Current()); } // Allow instrumentation its chance to hijack code. 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对象中去。
例如以下所看到的:
const void* OatFile::OatMethod::GetCode() const { return GetOatPointer<const void*>(code_offset_); } ...... void OatFile::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()); // Used by native methods in work around JNI mode. }这个函数定义在文件art/runtime/oat_file.cc中。当中。最重要的就是通过OatMethod类的成员函数GetCode获得OatMethod结构体中的code_offset_字段,而且通过调用ArtMethod类的成员函数SetEntryPointFromCompiledCode设置到參数method描写叙述的ArtMethod对象中去。OatMethod结构体中的code_offset_字段指向的是一个本地机器指令函数。这个本地机器指令函数正是通过翻译參数method描写叙述的类方法的DEX字节码得到的。
回到函数LinkCode中,它接着调用另外一个全局函数NeedsInterpreter检查參数method描写叙述的类方法是否须要通过解释器运行,它的实现例如以下所看到的:
// Returns true if the method must run with interpreter, false otherwise. static bool NeedsInterpreter(const mirror::ArtMethod* method, const void* code) { if (code == NULL) { // No code: need interpreter. return true; } ...... // If interpreter mode is enabled, every method (except native and proxy) must // be run with interpreter. return Runtime::Current()->GetInstrumentation()->InterpretOnly() && !method->IsNative() && !method->IsProxyMethod(); }这个函数定义在文件art/runtime/class_linker.cc中。在下面两种情况下,一个类方法须要通过解释器来运行:
1. 没有相应的本地机器指令,即參数code的值等于NULL。
2. ART虚拟机运行在解释模式中,而且类方法不是JNI方法,而且也不是代理方法。
调用Runtime类的静态成员函数Current获得的是描写叙述ART运行时的一个Runtime对象。
调用这个Runtime对象的成员函数GetInstrumentation获得的是一个Instrumentation对象。这个Instrumentation对象是用来调试ART运行时的,通过调用它的成员函数InterpretOnly能够知道ART虚拟机是否运行在解释模式中。
由于JNI方法是没有相应的DEX字节码的,因此即使ART虚拟机运行在解释模式中。JNI方法也不能通过解释器来运行。至于代理方法。由于是动态生成的(没有相应的DEX字节码),因此即使ART虚拟机运行在解释模式中,它们也不通过解释器来运行(这一点推測的,还没有确认)。
回到函数LinkCode中,假设调用函数NeedsInterpreter得到的返回值enter_interpreter等于true。那么就意味着參数method描写叙述的类方法须要通过解释器来运行。这时候就将函数artInterpreterToInterpreterBridge设置为解释器运行该类方法的入口点。
否则的话。就将另外一个函数artInterpreterToCompiledCodeBridge设置为解释器运行该类方法的入口点。
为什么我们须要为类方法设置解释器入口点呢?依据前面的分析能够知道,在ART虚拟机中,并非全部的类方法都是有相应的本地机器指令的,而且即使一个类方法有相应的本地机器指令,当ART虚拟机以解释模式运行时,它也须要通过解释器来运行。当以解释器运行的类方法在运行的过程中调用了其他的类方法时。解释器就须要进一步知道被调用的类方法是应用以解释方式运行。还是本地机器指令方法运行。为了能够进行统一处理。就给每个类方法都设置一个解释器入口点。须要通过解释运行的类方法的解释器入口点函数是artInterpreterToInterpreterBridge,它会继续通过解释器来运行该类方法。
须要通过本地机器指令运行的类方法的解释器入口点函数是artInterpreterToCompiledCodeBridge,它会间接地调用该类方法的本地机器指令。
函数LinkCode继续往下运行。推断參数method描写叙述的类方法是否是一个抽象方法。
抽象方法声明类中是没有实现的,必须要由子类实现。
因此抽象方法在声明类中是没有相应的本地机器指令的。它们必须要通过解释器来运行。只是,为了能够进行统一处理,我们仍然假装抽象方法有相应的本地机器指令函数。仅仅只是这个本地机器指令函数被设置为GetCompiledCodeToInterpreterBridge。
当函数GetCompiledCodeToInterpreterBridge被调用时。就会自己主动进入到解释器中去。
对于非抽象方法。函数LinkCode还要继续往下处理。到这里有一点是须要注意的。前面通过调用OatMethod类的成员函数LinkMethod,我们已经设置好參数method描写叙述的类方法的本地机器指令了。
可是。在下面两种情况下。我们须要进行调整:
1. 当參数method描写叙述的类方法是一个非类静态初始化函数(class initializer)的静态方法时,我们不能直接运行翻译其DEX字节码得到的本地机器指令。这是由于类静态方法能够在不创建类对象的前提下运行。这意味着一个类静态方法在运行的时候,相应的类可能还没有初始化好。这时候我们就须要先将相应的类初始化好。再运行相应的静态方法。为了能够做到这一点。
我们就先调用GetResolutionTrampoline函数得到一个Tampoline函数。接着将这个Trampoline函数作为静态方法的本地机器指令。
这样假设类静态方法在相应的类初始化前被调用。就会触发上述的Trampoline函数被运行。而当上述Trampoline函数运行时。它们先初始化好相应的类,再调用原来的类静态方法相应的本地机器指令。依照代码中的凝视,当一个类初始化完毕之后,就能够调用函数ClassLinker::FixupStaticTrampolines来修复该类的静态成员函数的本地机器指令。也是通过翻译DEX字节码得到的本地机器指令。这里须要注意的是,为什么类静态初始化函数不须要依照其他的类静态方法一样设置Tampoline函数呢?这是由于类静态初始化函数是一定保证是在类初始化过程中运行的。
2. 当參数method描写叙述的类方法须要通过解释器运行时,那么当该类方法运行时,就不能运行它的本地机器指令,因此我们就先调用GetCompiledCodeToInterpreterBridge函数获得一个桥接函数,而且将这个桥接函数假装为类方法的本地机器指令。
一旦该桥接函数被运行,它就会入到解释器去运行类方法。通过这样的方式。我们就能够以统一的方法来调用解释运行和本地机器指令运行的类方法。
函数LinkCode接下来继续推断參数method描写叙述的类方法是否是一个JNI方法。假设是的话,那么就调用ArtMethod类的成员函数UnregisterNative来初始化它的JNI方法调用接口。
ArtMethod类的成员函数UnregisterNative的实现例如以下所看到的:
void ArtMethod::UnregisterNative(Thread* self) { CHECK(IsNative()) << PrettyMethod(this); // restore stub to lookup native pointer via dlsym RegisterNative(self, GetJniDlsymLookupStub()); }这个函数定义在文件runtime/mirror/art_method.cc中。ArtMethod类的成员函数UnregisterNative实际上就是将一个JNI方法的初始化入口设置为通过调用函数GetJniDlsymLookupStub获得的一个Stub。
这个Stub的作用是。当一个JNI方法被调用时,假设还没有显示地注冊有Native函数,那么它就会自己主动从已载入的SO文件查找是否存在一个相应的Native函数。假设存在的话,就将它注冊为JNI方法的Native函数。而且运行它。这就是隐式的JNI方法注冊。
回到函数LinkCode,它最后调用Instrumentation类的成员函数UpdateMethodsCode检查是否要进一步改动參数method描写叙述的类方法的本地机器指令入口,它的实现例如以下所看到的:
void Instrumentation::UpdateMethodsCode(mirror::ArtMethod* method, const void* 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值,实际上指向的是一个Class对象。
有了类载入过程的知识后,接下来我们再继续分析类方法的查找过程,也就是分析JNI接口GetStaticMethodID的实现。依照前面的分析。JNI接口GetStaticMethodID是由JNI类的静态成员函数GetStaticMethodID实现的。
因此。接下来我们就開始分析JNI类的静态成员函数GetStaticMethodID的实现。
JNI类的静态成员函数GetStaticMethodID的实现例如以下所看到的:
class JNI { public: ...... static jmethodID GetStaticMethodID(JNIEnv* env, jclass java_class, const char* name, const char* sig) { CHECK_NON_NULL_ARGUMENT(GetStaticMethodID, java_class); CHECK_NON_NULL_ARGUMENT(GetStaticMethodID, name); CHECK_NON_NULL_ARGUMENT(GetStaticMethodID, sig); ScopedObjectAccess soa(env); return FindMethodID(soa, java_class, name, sig, true); } ...... };这个函数定义在文件art/runtime/jni_internal.cc中。參数name和sig描写叙述的各自是要查找的类方法的名称和签名,而參数java_class的是相应的类。參数java_class的类型是jclass,从前面类载入过程的分析能够知道,它实际上指向的是一个Class对象。
JNI类的静态成员函数GetStaticMethodID通过调用一个全局函数FindMethodID来查找指定的类,后者的实现例如以下所看到的:
static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class, const char* name, const char* sig, bool is_static) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { Class* c = soa.Decode<Class*>(jni_class); if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(c, true, true)) { return NULL; } ArtMethod* method = NULL; if (is_static) { method = c->FindDirectMethod(name, sig); } else { method = c->FindVirtualMethod(name, sig); if (method == NULL) { // No virtual method matching the signature. Search declared // private methods and constructors. method = c->FindDeclaredDirectMethod(name, sig); } } if (method == NULL || method->IsStatic() != is_static) { ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static"); return NULL; } return soa.EncodeMethod(method); }这个函数定义在文件art/runtime/jni_internal.cc。函数FindMethodID的运行步骤例如以下所看到的:
1. 将參数jni_class的值转换为一个Class指针c,因此就能够得到一个Class对象,而且通过ClassLinker类的成员函数EnsureInitialized确保该Class对象描写叙述的类已经初始化。
2. Class对象c描写叙述的类在载入的过程中,经过解析已经关联上一系列的成员函数。这些成员函数能够分为两类:Direct和Virtual。Direct类的成员函数包括全部的静态成员函数、私有成员函数和构造函数,而Virtual则包括全部的虚成员函数。因此:
2.1. 当參数is_static的值等于true时。那么就表示要查找的是静态成员函数,这时候就在Class对象c描写叙述的类的关联的Direct成员函数列表中查找參数name和sig相应的成员函数。这是通过调用Class类的成员函数FindDirectMethod来实现的。
2.2. 当參数is_static的值不等于true时,那么就表示要查找的是虚拟成员函数或者非静态的Direct成员函数,这时候先在Class对象c描写叙述的类的关联的Virtual成员函数列表中查找參数name和sig相应的成员函数。这是通过调用Class类的成员函数FindVirtualMethod来实现的。假设找不到相应的虚拟成员函数。那么再在Class对象c描写叙述的类的关联的Direct成员函数列表中查找參数name和sig相应的成员函数。
3. 经过前面的查找过程,假设都不能在Class对象c描写叙述的类中找到与參数name和sig相应的成员函数。那么就抛出一个NoSuchMethodError异常。
否则的话。就将查找得到的ArtMethod对象封装成一个jmethodID值返回给调用者。
也就是说。我们通过调用JNI接口GetStaticMethodID获得的不透明jmethodID值指向的实际上是一个ArtMethod对象。
得益于前面的类载入过程,当我们获得了一个ArtMethod对象之后,就能够轻松地得到它的本地机器指令入口,进而对它进行运行。
这样,我们就分析完毕类方法的查找过程了。在接下来的一篇文章中,我们将继续分析类方法的本地机器指令的调用过程。
通过对类方法的本地机器指令的调用过程的理解,能够进一步理解ART虚拟机的运行原理。敬请关注!很多其他信息也能够关注老罗的新浪微博:http://weibo.com/shengyangluo。
Android执行时ART载入类和方法的过程分析