1. 程式人生 > >Android執行時ART載入類和方法的過程分析

Android執行時ART載入類和方法的過程分析

        在前一篇文章中,我們通過分析OAT檔案的載入過程,認識了OAT檔案的格式,其中包含了原始的DEX檔案。既然ART執行時執行的都是翻譯DEX位元組碼後得到的本地機器指令了,為什麼還需要在OAT檔案中包含DEX檔案,並且將它載入到記憶體去呢?這是因為ART執行時提供了Java虛擬機器介面,而要實現Java虛擬機器介面不得不依賴於DEX檔案。本文就通過分析ART執行時載入類及其方法的過程來理解DEX檔案的作用。

《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檔案的過程分析這篇文章,就會很容易知道,OAT檔案裡面包含有DEX檔案。而且ART執行時在啟動的時候,會載入一個名稱為[email protected][email protected]@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檔案的過程分析一文。

       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載入方法過程分析

        在前一篇文章中,我們通過分析OAT檔案的載入過程,認識了OAT檔案的格式,其中包含了原始的DEX檔案。既然ART執行時執行的都是翻譯DEX位元組碼後得到的本地機器指令了,為什麼還需要在OAT檔案中包含DEX檔案,並且將它載入到記憶體去呢?這是因為ART執行時提

Android執行ART載入OAT檔案的過程分析

                        在前面一文中,我們介紹了Android執行時ART,它的核心是OAT檔案。OAT檔案是一種Android私有ELF檔案格式,它不僅包含有從DEX檔案翻譯而來的本地機器指令,還包含有原來的DEX檔案內容。這使得我們無需重新編譯原有的APK就可以讓它正常地在ART裡

利用Objective-C的反射機制執行特性實現靜態方法的動態訪問(一)

如題,灑家今天在搭建蘋果手機APP開發框架中遇到一個坑爹問題,折騰了半天,總算研究出來了,特記錄如下: 1、先說具體需求,本人實現了一個自定義檢視控制元件,通過KVC特性先從plist配置檔案中讀取資料,轉換成模型物件,然後根據模型物件動態建立檢視物件,這時就需要用到Obj

利用Objective-C的反射機制執行特性實現靜態方法的動態訪問(二)

繼上次的研究成果繼續深入研究,灑家又完善了下在執行時動態呼叫所有OC類方法的公用方法: typedef void*(*ObjcMsgSend)(id, SEL, ...); - (void *)invoke:(id)inst method:(NSString *)nam

Java執行動態載入之ClassLoader

https://blog.csdn.net/fjssharpsword/article/details/64922083 ************************************************************ 需求場景:動態載入類ClassLoaderd,在

java中執行無法載入的情況

問題一: cmd執行時找不到或無法載入類,但是可以正常編譯,eclipse也可以正常執行 可能情況classpath路徑問題: 絕對路徑:C:\Program Files\Java\jdk1.8.0_131\lib;C:\Program Files\Java\j

ART執行垃圾收集(GC)過程分析

  ART執行時與Dalvik虛擬機器一樣,都使用了Mark-Sweep演算法進行垃圾回收,因此它們的垃圾回收流程在總體上是一致的。但是ART執行時對堆的劃分更加細緻,因而在此基礎上實現了更多樣的回收策略。不同的策略有不同的回收力度,力度越大的回收策略,每次回收的記憶

轉自老羅 Android應用程式資源的編譯打包過程分析

原文地址   http://blog.csdn.net/luoshengyang/article/details/8744683 轉載自老羅,轉載請說明   我們知道,在一個APK檔案中,除了有程式碼檔案之外,還有很多資原始檔。這些資原始檔是通過An

Android應用程式資源的編譯打包過程分析

   我們知道,在一個APK檔案中,除了有程式碼檔案之外,還有很多資原始檔。這些資原始檔是通過Android資源打包工具aapt(Android Asset Package Tool)打包到APK檔案裡面的。在打包之前,大部分文字格式的XML資原始檔還會被編譯

java動態載入jar包,並執行其中的方法

動態載入jar包,在實際開發中經常會需要用到,尤其涉及平臺和業務的關係的時候,業務邏輯部分可以獨立出去交給業務方管理,業務方只需要提供jar包,就能在平臺上執行。 下面通過一個例項來直觀演示: 第一:定義一個抽象類 AbstractAction (稍後換成介面的例項) pa

java動態編譯class,動態載入執行載入方法,直接可執行測試

直接上圖上程式碼 public static void main(String[] args) { TestClass testClass=new TestClass(); try { //動態編譯程式碼 Java

Android 執行: DVM vs ART

在瞭解 Android 執行時之前,我們需要了解什麼是執行時環境以及一些基本概念,即 Java 虛擬機器(JVM)和 Dalvik 虛擬機器(DVM)的功能。 什麼是執行時? 簡單來說,執行時就是一個供作業系統使用的系統,它負責將你用高階語言(比如 Java)編

Android程式設計師必會技能---執行動態生成---之動態代理

談到java中的動態生成一個類,主要分為兩種方法,一種就是動態代理,另外一種就是asm。今天我們就來把對第一種方法 也就是動態代理生成類,這個流程搞清楚吃透。 要搞清楚動態代理,首先要弄明白為什麼需要動態代理?靜態代理不夠用嗎? 首先考慮一個場景,團隊中git提交的時候是不是都要經過leader revi

Android NDK(JNI)學習總結一:Java程式碼中申明native函式-Java呼叫C函式,並在C函式中訪問java方法、屬性

本文不涉及android-ndk開發環境搭。 步驟一:新建一個APP,名稱為HelloJNI,然後定義一個類(將會在native程式碼中呼叫和訪問該類): package com.example.hellojni; public class JNITe

Android開發中一些被冷落但卻很有用的方法

來自:http://luckyandyzhang.github.io/ Resources.getIdentifier : 這個我 用過,記得以前做過一個面板切換功能,可以通過這個方法從面板包 獲取面板資源。 (面板包的資源名稱和 主包的資源名稱id 名是一樣的

十、JAVA多執行緒:JVM載入器(自動載入器、雙親委託機制、載入器名稱空間、執行包、的解除安裝等)

  Jvm提供了三大內建的類載入器,不同的類載入器負責將不同的類載入到記憶體之中 根載入器(Bootstrap ClassLoader) 是最頂層的載入器,是由C++編寫的,主要負責虛擬機器核心類庫的載入,如整個java.lang包,根載入器是獲取不到引用的,因此

Android.執行緒池的原理執行緒池管理的使用

執行緒池的原理 執行緒池使用來管理執行緒的,之所以稱為池,是因為其可以管理多條執行緒,所以需要用一個集合來管理執行緒,然後執行緒池是有大小的,當一個執行緒池管理的執行緒數目為計算機的cup數*2+1個

java之多執行緒中ThreadRunnable介面使用方法

java提供了兩種執行緒方式,一種是繼承java.lang包下的Thread類,覆寫Thread類的run()方法,在run()方法中實現執行線上程上的程式碼!第二種是實現Runnable介面建立多執行

方法上加事務

入庫 temp spring ise work order -1 oid mes package com.yundaex.wms.config; import org.springframework.beans.factory.annotation.Autowired;

Interllij IDEA 註釋模板(方法

生成 eight 否則 return pla mage clas templates rip 類上的註釋:   file->setting->Editor->Filr and Code Templates->Includes->File Hea