目錄
一,概述
二,ART運行時的入口
一,概述
既然ART運行時執行的都是翻譯DEX字節碼后得到的本地機器指令了,為什么還需要在OAT文件中包含DEX文件,并且將它加載到內存去呢?這是因為ART運行時提供了Java虛擬機接口,而要實現Java虛擬機接口不得不依賴于DEX文件
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_,計算出該方法對應的本地機器指令
二,ART運行時的入口
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{ALOGD(">>>>>> START %s uid %d <<<<<<\n",className != NULL ? className : "(unknown)", getuid());static const String8 startSystemServer("start-system-server");/** 'startSystemServer == true' means runtime is obsolete and not run from* init.rc anymore, so we print out the boot start event here.*/for (size_t i = 0; i < options.size(); ++i) {if (options[i] == startSystemServer) {/* track our progress through the boot sequence */const int LOG_BOOT_PROGRESS_START = 3000;LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START, ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));}}const char* rootDir = getenv("ANDROID_ROOT");if (rootDir == NULL) {rootDir = "/system";if (!hasDir("/system")) {LOG_FATAL("No root directory specified, and /android does not exist.");return;}setenv("ANDROID_ROOT", rootDir, 1);}//const char* kernelHack = getenv("LD_ASSUME_KERNEL");//ALOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack);/* start the virtual machine */JniInvocation jni_invocation;jni_invocation.Init(NULL);JNIEnv* env;if (startVm(&mJavaVM, &env, zygote) != 0) {return;}onVmCreated(env);/** Register android functions.*/if (startReg(env) < 0) {‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’
}
首先是通過調用函數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的實現之前,我們先要講清楚JNI接口是如何創建的
extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {return JniInvocation::GetJniInvocation().JNI_CreateJavaVM(p_vm, p_env, vm_args);
}
JNI類的靜態成員函數FindClass的實現如下所示
/Volumes/aosp/android-8.1.0_r52/art/runtime/jni_internal.cc
static jclass FindClass(JNIEnv* env, const char* name) {CHECK_NON_NULL_ARGUMENT(name);Runtime* runtime = Runtime::Current();ClassLinker* class_linker = runtime->GetClassLinker();std::string descriptor(NormalizeJniClassDescriptor(name));ScopedObjectAccess soa(env);mirror::Class* c = nullptr;if (runtime->IsStarted()) {StackHandleScope<1> hs(soa.Self());Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetClassLoader(soa)));c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader);} else {c = class_linker->FindSystemClass(soa.Self(), descriptor.c_str());}return soa.AddLocalReference<jclass>(c);}
NI類的靜態成員函數FindClass首先是判斷ART運行時是否已經啟動起來。如果已經啟動,那么就通過調用函數GetClassLoader來獲得當前線程所關聯的ClassLoader,并且以此為參數,調用前面獲得的ClassLinker對象的成員函數FindClass來加載由參數name指定的類。一般來說,當前線程所關聯的ClassLoader就是當前正在執行的類方法所關聯的ClassLoader,即用來加載當前正在執行的類的ClassLoader。如果ART虛擬機還沒有開始執行類方法,就像我們現在這個場景,那么當前線程所關聯的ClassLoader實際上就系統類加載器,即SystemClassLoader。
? ? ? ?如果ART運行時還沒有啟動,那么這時候只可以加載系統類。這個通過前面獲得的ClassLinker對象的成員函數FindSystemClass來實現的。在我們這個場景中,ART運行時已經啟動,因此,接下來我們就繼續分析ClassLinker類的成員函數FindClass的實現。
? ? ? ? ClassLinker類的成員函數FindClass的實現如下所示:
?/Volumes/aosp/android-8.1.0_r52/art/runtime/class_linker.cc
mirror::Class* ClassLinker::FindClass(Thread* self,const char* descriptor,Handle<mirror::ClassLoader> class_loader) {DCHECK_NE(*descriptor, '\0') << "descriptor is empty string";DCHECK(self != nullptr);self->AssertNoPendingException();self->PoisonObjectPointers(); // For DefineClass, CreateArrayClass, etc...if (descriptor[1] == '\0') {// only the descriptors of primitive types should be 1 character long, also avoid class lookup// for primitive classes that aren't backed by dex files.return FindPrimitiveClass(descriptor[0]);}const size_t hash = ComputeModifiedUtf8Hash(descriptor);// Find the class in the loaded classes table.ObjPtr<mirror::Class> klass = LookupClass(self, descriptor, hash, class_loader.Get());if (klass != nullptr) {return EnsureResolved(self, descriptor, klass);}// Class is not yet loaded.if (descriptor[0] != '[' && class_loader == nullptr) {// Non-array class and the boot class loader, search the boot class path.ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_);if (pair.second != nullptr) {return DefineClass(self,descriptor,hash,ScopedNullHandle<mirror::ClassLoader>(),*pair.first,*pair.second);} else {// The boot class loader is searched ahead of the application class loader, failures are// expected and will be wrapped in a ClassNotFoundException. Use the pre-allocated error to// trigger the chaining with a proper stack trace.ObjPtr<mirror::Throwable> pre_allocated =Runtime::Current()->GetPreAllocatedNoClassDefFoundError();self->SetException(pre_allocated);return nullptr;}}ObjPtr<mirror::Class> result_ptr;bool descriptor_equals;if (descriptor[0] == '[') {result_ptr = CreateArrayClass(self, descriptor, hash, class_loader);DCHECK_EQ(result_ptr == nullptr, self->IsExceptionPending());DCHECK(result_ptr == nullptr || result_ptr->DescriptorEquals(descriptor));descriptor_equals = true;} else {ScopedObjectAccessUnchecked soa(self);bool known_hierarchy =FindClassInBaseDexClassLoader(soa, self, descriptor, hash, class_loader, &result_ptr);if (result_ptr != nullptr) {// The chain was understood and we found the class. We still need to add the class to// the class table to protect from racy programs that can try and redefine the path list// which would change the Class<?> returned for subsequent evaluation of const-class.DCHECK(known_hierarchy);DCHECK(result_ptr->DescriptorEquals(descriptor));descriptor_equals = true;} else {// Either the chain wasn't understood or the class wasn't found.//// If the chain was understood but we did not find the class, let the Java-side// rediscover all this and throw the exception with the right stack trace. Note that// the Java-side could still succeed for racy programs if another thread is actively// modifying the class loader's path list.if (!self->CanCallIntoJava()) {// Oops, we can't call into java so we can't run actual class-loader code.// This is true for e.g. for the compiler (jit or aot).ObjPtr<mirror::Throwable> pre_allocated =Runtime::Current()->GetPreAllocatedNoClassDefFoundError();self->SetException(pre_allocated);return nullptr;}
''''''''''''''''''''''''''''''''''''''''''''''''''''''''
}
ClassLinker類的成員函數FindClass首先是調用另外一個成員函數LookupClass來檢查參數descriptor指定的類是否已經被加載過。如果是的話,那么ClassLinker類的成員函數LookupClass就會返回一個對應的Class對象,這個Class對象接著就會返回給調用者,表示加載已經完成
?
知道了參數descriptor指定的類定義在哪一個DEX文件之后,就可以通過ClassLinker類的另外一個成員函數DefineClass來從中加載它了。接下來,我們就繼續分析ClassLinker類的成員函數DefineClass的實現
mirror::Class* ClassLinker::DefineClass(Thread* self,const char* descriptor,size_t hash,Handle<mirror::ClassLoader> class_loader,const DexFile& dex_file,const DexFile::ClassDef& dex_class_def) {StackHandleScope<3> hs(self);auto klass = hs.NewHandle<mirror::Class>(nullptr);// 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.Assign(GetClassRoot(kJavaLangObject));} else if (strcmp(descriptor, "Ljava/lang/Class;") == 0) {klass.Assign(GetClassRoot(kJavaLangClass));} else if (strcmp(descriptor, "Ljava/lang/String;") == 0) {klass.Assign(GetClassRoot(kJavaLangString));} else if (strcmp(descriptor, "Ljava/lang/ref/Reference;") == 0) {klass.Assign(GetClassRoot(kJavaLangRefReference));} else if (strcmp(descriptor, "Ljava/lang/DexCache;") == 0) {klass.Assign(GetClassRoot(kJavaLangDexCache));} else if (strcmp(descriptor, "Ldalvik/system/ClassExt;") == 0) {klass.Assign(GetClassRoot(kDalvikSystemClassExt));}}if (klass == nullptr) {// Allocate a class with the status of not ready.// Interface object should get the right size here. Regular class will// figure out the right size later and be replaced with one of the right// size when the class becomes resolved.klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def)));}if (UNLIKELY(klass == nullptr)) {self->AssertPendingOOMException();return nullptr;}
調用ClassLinker類的成員函數DefineClass的時候,如果ClassLinker正處于初始化過程,即其成員變量init_done_的值等于false,并且參數descriptor描述的是特定的內部類,那么就將本地變量klass指向它們,其余情況則會通過成員函數AllocClass為其分配存儲空間,以便后面通過成員函數LoadClass進行初始化
?
接下來,我們主要分析ClassLinker類的成員函數LoadClass的實現,以便可以了解類的加載過程。
??????? ClassLinker類的成員函數LoadClass的實現如下所示
void ClassLinker::LoadClass(Thread* self,const DexFile& dex_file,const DexFile::ClassDef& dex_class_def,Handle<mirror::Class> klass) {const uint8_t* class_data = dex_file.GetClassData(dex_class_def);if (class_data == nullptr) {return; // no fields or methods - for example a marker interface}LoadClassMembers(self, dex_file, class_data, klass);
}
void ClassLinker::LoadClassMembers(Thread* self,const DexFile& dex_file,const uint8_t* class_data,Handle<mirror::Class> klass) {{// Note: We cannot have thread suspension until the field and method arrays are setup or else// Class::VisitFieldRoots may miss some fields or methods.ScopedAssertNoThreadSuspension nts(__FUNCTION__);// Load static fields.// We allow duplicate definitions of the same field in a class_data_item// but ignore the repeated indexes here, b/21868015.LinearAlloc* const allocator = GetAllocatorForClassLoader(klass->GetClassLoader());ClassDataItemIterator it(dex_file, class_data);LengthPrefixedArray<ArtField>* sfields = AllocArtFieldArray(self,allocator,it.NumStaticFields());size_t num_sfields = 0;uint32_t last_field_idx = 0u;for (; it.HasNextStaticField(); it.Next()) {uint32_t field_idx = it.GetMemberIndex();DCHECK_GE(field_idx, last_field_idx); // Ordering enforced by DexFileVerifier.if (num_sfields == 0 || LIKELY(field_idx > last_field_idx)) {DCHECK_LT(num_sfields, it.NumStaticFields());LoadField(it, klass, &sfields->At(num_sfields));++num_sfields;last_field_idx = field_idx;}}// Load instance fields.LengthPrefixedArray<ArtField>* ifields = AllocArtFieldArray(self,allocator,it.NumInstanceFields());size_t num_ifields = 0u;last_field_idx = 0u;for (; it.HasNextInstanceField(); it.Next()) {uint32_t field_idx = it.GetMemberIndex();DCHECK_GE(field_idx, last_field_idx); // Ordering enforced by DexFileVerifier.if (num_ifields == 0 || LIKELY(field_idx > last_field_idx)) {DCHECK_LT(num_ifields, it.NumInstanceFields());LoadField(it, klass, &ifields->At(num_ifields));++num_ifields;last_field_idx = field_idx;}}
'''''''''''''''
}
們首先要明確一下各個參數的含義:
? ? ? ?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對象去,以便可以得到一個完整的已加載類信息。
static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class,const char* name, const char* sig, bool is_static)REQUIRES_SHARED(Locks::mutator_lock_) {ObjPtr<mirror::Class> c = EnsureInitialized(soa.Self(), soa.Decode<mirror::Class>(jni_class));if (c == nullptr) {return nullptr;}ArtMethod* method = nullptr;auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();if (c->IsInterface()) {method = c->FindInterfaceMethod(name, sig, pointer_size);} else {method = c->FindClassMethod(name, sig, pointer_size);}if (method == nullptr || method->IsStatic() != is_static) {ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static");return nullptr;}return jni::EncodeArtMethod(method);
}
函數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對象之后,就可以輕松地得到它的本地機器指令入口,進而對它進行執行
?