<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [原文出處-----------Android運行時ART加載類和方法的過程分析](http://blog.csdn.net/luoshengyang/article/details/39533503) 在前一篇文章中,我們通過分析OAT文件的加載過程,認識了OAT文件的格式,其中包含了原始的DEX文件。既然ART運行時執行的都是翻譯DEX字節碼后得到的本地機器指令了,為什么還需要在OAT文件中包含DEX文件,并且將它加載到內存去呢?這是因為ART運行時提供了Java虛擬機接口,而要實現Java虛擬機接口不得不依賴于DEX文件。本文就通過分析ART運行時加載類及其方法的過程來理解DEX文件的作用。 在前面Android運行時ART加載OAT文件的過程分析這篇文章的最后,我們簡單總結了ART運行時查找類方法的本地機器指令的過程,如圖1所示: ![](https://box.kancloud.cn/b76fe4caf2fffb8dcd2a66aea58a12ea_790x303.png) 圖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簡要介紹和學習計劃](http://blog.csdn.net/luoshengyang/article/details/39256813)一文的學習,我們可以知道,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文件的過程分析](http://blog.csdn.net/luoshengyang/article/details/39307813)一文可以知道,與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接口,如下所示: [cpp] view plain copy 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虛擬機的啟動過程分析](http://blog.csdn.net/luoshengyang/article/details/8885792)這篇文章,那么對上述的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文件的過程分析](http://blog.csdn.net/luoshengyang/article/details/39307813)一文可以知道。上述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文件的過程分析](http://blog.csdn.net/luoshengyang/article/details/39307813)這篇文章,就會很容易知道,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 = dex_file.GetClassData(dex_class_def); ...... ClassDataItemIterator it(dex_file, class_data); Thread* self = Thread::Current(); if (it.NumStaticFields() != 0) { 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_t i = 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_t i = 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<const OatFile::OatClass> oat_class; if (Runtime::Current()->IsStarted() && !Runtime::Current()->UseCompileTimeClassPath()) { oat_class.reset(GetOatClass(dex_file, klass->GetDexClassDefIndex())); } // Load methods. if (it.NumDirectMethods() != 0) { // TODO: append direct methods to class object mirror::ObjectArray<mirror::ArtMethod>* directs = AllocArtMethodArray(self, it.NumDirectMethods()); ...... klass->SetDirectMethods(directs); } if (it.NumVirtualMethods() != 0) { // TODO: append direct methods to class object mirror::ObjectArray<mirror::ArtMethod>* virtuals = AllocArtMethodArray(self, it.NumVirtualMethods()); ...... klass->SetVirtualMethods(virtuals); } size_t class_def_method_index = 0; for (size_t i = 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_t i = 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類的成員函數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文件的過程分析](http://blog.csdn.net/luoshengyang/article/details/39307813)一文。 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文件的過程分析](http://blog.csdn.net/luoshengyang/article/details/39307813)一文。 5. 從參數dex_file描述的DEX文件中獲得正在加載的類的直接成員函數和虛擬成員函數個數,并且為每一個直接成員函數和虛擬成員函數都分配一個ArtMethod對象,接著通過ClassLinker類的成員函數LoadMethod對這些ArtMethod對象進行初始化。初始好得到的ArtMethod對象全部保存在klass描述的Class對象中。 6. 每一個直接成員函數和虛擬成員函數都對應有一個函數索引號。根據這個函數索引號可以在第4步得到的OatClass結構體中找到對應的本地機器指令,具體可以參考前面圖1和[Android運行時ART加載OAT文件的過程分析](http://blog.csdn.net/luoshengyang/article/details/39307813)一文。所有與這些成員函數關聯的本地機器指令信息通過全局函數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虛擬機的運行原理。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看