




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
Android運(yùn)行時(shí)ART加載類和方法的過程分析在前一篇文章中,我們通過分析OAT文件的加載過程,認(rèn)識(shí)了OAT文件的格式,其中包含了原始的DEX文件。既然ART運(yùn)行時(shí)執(zhí)行的都是翻譯DEX字節(jié)碼后得到的本地機(jī)器指令了,為什么還需要在 OAT文件中包含 DEX文件,并且將它加載到內(nèi)存去呢?這是因?yàn)?ART運(yùn)行時(shí)提供了 Java虛擬機(jī)接口,而要實(shí)現(xiàn) Java虛擬機(jī)接口不得不依賴于 DEX文件。本文就通過分析 ART運(yùn)行時(shí)加載類及其方法的過程來理解 DEX文件的作用。在前面這篇文章的最后,我們簡(jiǎn)單總結(jié)了 ART運(yùn)行時(shí)查找類方法的本地機(jī)器指令的過程,如圖1所示:為了方便描述,我們將 DEX文件中描述的類和方法稱為 DEX類(DexClass)和DEX方法(DexMethod),而將在OAT文件中描述的類和方法稱為 OAT類(OatClass)和OAT方法(OatMethod)。接下來我們還會(huì)看到,ART運(yùn)行時(shí)在內(nèi)部又會(huì)使用另外兩個(gè)不同的術(shù)語來描述類和方法,其中將類描述為Class,而將類方法描述為ArtMethod。在圖1中,為了找到一個(gè)類方法的本地機(jī)器指令,我們需要執(zhí)行以下的操作:1.在DEX文件中找到目標(biāo) DEX類的編號(hào),并且以這個(gè)編號(hào)為索引,在 OAT文件中找到對(duì)應(yīng)的 OAT類。在DEX文件中找到目標(biāo)DEX方法的編號(hào),并且以這個(gè)編號(hào)為索引,在上一步找到的OAT類中找到對(duì)應(yīng)的OAT方法。使用上一步找到的OAT方法的成員變量begin_和code_offset_,計(jì)算出該方法對(duì)應(yīng)的本地機(jī)器指令。通過前面一文的學(xué)習(xí),我們可以知道,ART運(yùn)行時(shí)的入口是類的靜態(tài)成員函數(shù)main,如下所示:[cpp]viewplaincopy 在CODE上查看代碼片派生到我的代碼片voidAndroidRuntime::start(constchar*className,constchar*options){....../*startthevirtualmachine*/JniInvocationjni_invocation;jni_invocation.Init(NULL);JNIEnv*env;if(startVm(&mJavaVM,&env)!=0){return;}....../**StartVM. ThisthreadbecomesthemainthreadoftheVM,andwillnotreturnuntiltheVMexits.*/char*slashClassName=toSlashClassName(className);jclassstartClass=env->FindClass(slashClassName);if(startClass==NULL){ALOGE("JavaVMunabletolocateclass'%s'\n",slashClassName);/*keepgoing*/}else{jmethodIDstartMeth=env->GetStaticMethodID(startClass,"main","([Ljava/lang/String;)V");if(startMeth==NULL){ALOGE("JavaVMunabletofindmain()in'%s'\n",className);/*keepgoing*/}else{env->CallStaticVoidMethod(startClass,startMeth,strArray);......}}......}這個(gè)函數(shù)定義在文件在AndroidRuntimeJava虛擬機(jī)mJavaVM及其來的描述中,我們將不區(qū)分獲得了 ART 虛擬機(jī)的
frameworks/base/core/jni/AndroidRuntime.cpp 中。類的成員函數(shù) start中,首先是通過調(diào)用函數(shù) startVm創(chuàng)建了一個(gè)JNI接口env。這個(gè)Java虛擬機(jī)實(shí)際上就是 ART運(yùn)行時(shí)。在接下ART虛擬機(jī)和ART運(yùn)行時(shí),并且認(rèn)為它們表達(dá)的是同一個(gè)概念。JNI 接口之后,就可以通過它提供的函數(shù) FindClass 和GetStaticMethodID來加載最后就可以再通過
JNI 接口提供的函數(shù)
類及其靜態(tài)成員函數(shù)CallStaticVoidMethod
main。于是,來調(diào)用類的靜態(tài)成員函數(shù)main,以及進(jìn)行到ART虛擬機(jī)里面去運(yùn)行。接下來,我們就通過分析 JNI接口FindClass和GetStaticMethodID的實(shí)現(xiàn),以便理解ART運(yùn)行時(shí)是如何查找到指定的類和方法的。在接下來的一篇文章中,我們?cè)俜治鲞\(yùn)行時(shí)是如何通過 JNI接口CallStaticVoidMethod來執(zhí)行指定類方法的本地機(jī)器指令的。
ART在分析JNI接口FindClass和GetStaticMethodID的實(shí)現(xiàn)之前,我們先要講清楚 JNI接口是如何創(chuàng)建的。從前面一文可以知道,與 ART虛擬機(jī)主線程關(guān)聯(lián)的 JNI接口是在函數(shù)JNI_CreateJavaVM中創(chuàng)建的,如下所示:[cpp]viewplaincopy 在CODE上查看代碼片派生到我的代碼片extern"C"jintJNI_CreateJavaVM(JavaVM**p_vm,JNIEnv**p_env,void*vm_args){......*p_env=Thread::Current()->GetJniEnv();......returnJNI_OK;}這個(gè)函數(shù)定義在文件 art/runtime/jni_internal.cc 中。調(diào)用Thread類的靜態(tài)成員函數(shù) Current獲得的是用來描述當(dāng)前線程的主線程)的一個(gè) Thread對(duì)象,再通過調(diào)用這個(gè) Thread對(duì)象的成員函數(shù)一個(gè)JNI接口,并且保存在輸出參數(shù) p_env中。
(即ARTGetJniEnv
虛擬機(jī)就獲得Thread
類的成員函數(shù)
GetJniEnv
的實(shí)現(xiàn)如下所示:[cpp]viewplaincopy在CODEclassPACKED(4)Thread{
上查看代碼片派生到我的代碼片public:......//JNImethodsJNIEnvExt*GetJniEnv()const{returnjni_env_;}......private:......EverythreadmayhaveanassociatedJNIenvironmentJNIEnvExt*jni_env_;......};這個(gè)函數(shù)定義在文件
art/runtime/thread.h
中。Thread類的成員函數(shù)
GetJniEnv
返回的是成員變量
jni_env_指向的一個(gè)
JNIEnvExt
對(duì)象。JNIEnvExt類是從JNIEnv類繼承下來的,如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片structJNIEnvExt:publicJNIEnv{......};這個(gè)類定義在文件 art/runtime/jni_internal.h。JNIEnv類定義了JNI接口,如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片typedef_JNIEnvJNIEnv;......struct_JNIEnv{/*donotrenamethis;itdoesnotseemtobeentirelyopaque*/conststructJNINativeInterface*functions;......jintGetVersion(){returnfunctions->GetVersion(this);}......};這個(gè)類定義在文件 libnativehelper/include/nativehelper/jni.h 中。在JNIEnv 類中,最重要的就是成員變量 functions 了,它指向的是一個(gè)類型為JNINativeInterface的JNI函數(shù)表。所有的 JNI接口調(diào)用都是通過這個(gè) JNI函數(shù)表來實(shí)現(xiàn)的。例如,用來獲得版本號(hào)的 JNI接口GetVersion就是通過調(diào)用 JNI函數(shù)表中的 GetVersion函數(shù)來實(shí)現(xiàn)的。那么,上述的
JNI
函數(shù)表是如何創(chuàng)建的呢?通過
JNIEnvExt
類的構(gòu)造函數(shù)可以知道答案,如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片JNIEnvExt::JNIEnvExt(Thread*self,JavaVMExt*vm):......{functions=unchecked_functions=&gJniNativeInterface;......}這個(gè)函數(shù)定義在文件 art/runtime/jni_internal.cc 中。JNIEnvExt 類的構(gòu)造函數(shù)將父類 JNIEnv 的成員變量gJniNativeInterface。也就是說,JNI函數(shù)表實(shí)際是由全局變量
functions初始化為全局變量gJniNativeInterface來描述的。全局變量
gJniNativeInterface
的定義如下所示:[cpp]viewplaincopy 在CODE上查看代碼片派生到我的代碼片constJNINativeInterfacegJniNativeInterface={NULL, //reserved0.NULL, //reserved1.NULL, //reserved2.NULL, //reserved3.JNI::GetVersion,......JNI::FindClass,......JNI::GetStaticMethodID,......JNI::CallStaticVoidMethod,......};這個(gè)全局變量定義在文件 art/runtime/jni_internal.cc 中。從這里可以看出, JNI函數(shù)表實(shí)際上是由 JNI類的靜態(tài)成員函數(shù)組成的。例如, JNI函數(shù)GetVersion是由JNI類的靜態(tài)成員函數(shù) GetVersion來實(shí)現(xiàn)的。理解了這一點(diǎn)之后, 我們就輕松地知道同接下來我們要分析的JNI接口FindClass和GetStaticMethodID分別是由JNI類的靜態(tài)成員函數(shù)FindClass和GetStaticMethodID來實(shí)現(xiàn)的。事實(shí)上,如果讀者看過這篇文章,那么對(duì)上述的JNI接口定義是一目了然的。JNI類的靜態(tài)成員函數(shù) FindClass的實(shí)現(xiàn)如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片classJNI{public:......staticjclassFindClass(JNIEnv*env,constchar*name){CHECK_NON_NULL_ARGUMENT(FindClass,name);Runtime*runtime=Runtime::Current();ClassLinker*class_linker=runtime->GetClassLinker();std::stringdescriptor(NormalizeJniClassDescriptor(name));ScopedObjectAccesssoa(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());}returnsoa.AddLocalReference<jclass>(c);}......};這個(gè)函數(shù)定義在文件 art/runtime/jni_internal.cc 中。在ART虛擬機(jī)進(jìn)程中,存在著一個(gè)Runtime單例,用來描述ART運(yùn)行時(shí)。通過調(diào)用Runtime類的靜態(tài)成員函數(shù)Current可以獲得上述Runtime單例。獲得了這個(gè)單例之后,就可以調(diào)用它的成員函數(shù)GetClassLinker來獲得一個(gè)ClassLinker對(duì)象。從前面一文可以知道。上述ClassLinker對(duì)象是在創(chuàng)建ART虛擬機(jī)的過程中創(chuàng)建的,用來加載類以及鏈接類方法。JNI類的靜態(tài)成員函數(shù) FindClass首先是判斷 ART運(yùn)行時(shí)是否已經(jīng)啟動(dòng)起來。如果已經(jīng)啟動(dòng),那么就通過調(diào)用函數(shù) GetClassLoader來獲得當(dāng)前線程所關(guān)聯(lián)的 ClassLoader,并且以此為參數(shù),調(diào)用前面獲得的 ClassLinker對(duì)象的成員函數(shù) FindClass來加載由參數(shù) name指定的類。一般來說,當(dāng)前線程所關(guān)聯(lián)的 ClassLoader就是當(dāng)前正在執(zhí)行的類方法所關(guān)聯(lián)的ClassLoader,即用來加載當(dāng)前正在執(zhí)行的類的 ClassLoader。如果ART虛擬機(jī)還沒有開始執(zhí)行類方法,就像我們現(xiàn)在這個(gè)場(chǎng)景,那么當(dāng)前線程所關(guān)聯(lián)的 ClassLoader實(shí)際上就系統(tǒng)類加載器,即 SystemClassLoader。如果ART運(yùn)行時(shí)還沒有啟動(dòng),那么這時(shí)候只可以加載系統(tǒng)類。這個(gè)通過前面獲得的ClassLinker對(duì)象的成員函數(shù)FindSystemClass來實(shí)現(xiàn)的。在我們這個(gè)場(chǎng)景中,ART運(yùn)行時(shí)已經(jīng)啟動(dòng),因此,接下來我們就繼續(xù)分析 ClassLinker類的成員函數(shù) FindClass的實(shí)現(xiàn)。ClassLinker類的成員函數(shù) FindClass的實(shí)現(xiàn)如下所示:[cpp]viewplaincopy 在CODE上查看代碼片派生到我的代碼片mirror::Class*ClassLinker::FindClass(constchar*descriptor,mirror::ClassLoader*class_loader){......Thread*self=Thread::Current();......//Findtheclassintheloadedclassestable.mirror::Class*klass=LookupClass(descriptor,class_loader);if(klass!=NULL){returnEnsureResolved(self,klass);}//Classisnotyetloaded.if(descriptor[0]=='['){......}elseif(class_loader==NULL){DexFile::ClassPathEntrypair=DexFile::FindInClassPath(descriptor,boot_class_path_);if(pair.second!=NULL){returnDefineClass(descriptor,NULL,*pair.first,*pair.second);}}elseif(Runtime::Current()->UseCompileTimeClassPath()){......}else{ScopedObjectAccessUncheckedsoa(self->GetJniEnv());ScopedLocalRef<jobject>class_loader_object(soa.Env(),soa.AddLocalReference<jobject>(class_loader));std::stringclass_name_string(DescriptorToDot(descriptor));ScopedLocalRef<jobject>result(soa.Env(),NULL);{ScopedThreadStateChangetsc(self,kNative);ScopedLocalRef<jobject>class_name_object(soa.Env(),soa.Env()->NewStringUTF(class_name_string.c_str()));if(class_name_object.get()==NULL){returnNULL;}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()){IftheClassLoaderthrew,passthatexceptionup.returnNULL;}elseif(result.get()==NULL){brokenloader-throwNPEtobecompatiblewithDalvikThrowNullPointerException(NULL,StringPrintf("ClassLoader.loadClassfor%s",
returned nullclass_name_string.c_str()).c_str());returnNULL;}else{//success,returnmirror::Class*returnsoa.Decode<mirror::Class*>(result.get());}}ThrowNoClassDefFoundError("Class%snotfound",PrintableString(descriptor).c_str());returnNULL;}這個(gè)函數(shù)定義在文件 art/runtime/class_linker.cc中。參數(shù)descriptor指向的是要加載的類的簽名,而參數(shù)載器,我們假設(shè)它的值不為空,并且指向系統(tǒng)類加載器。
class_loader指向的是一個(gè)類加ClassLinker類的成員函數(shù)FindClass首先是調(diào)用另外一個(gè)成員函數(shù)LookupClass來檢查參數(shù)descriptor指定的類是否已經(jīng)被加載過。如果是的話,那么ClassLinker類的成員函數(shù)LookupClass
就會(huì)返回一個(gè)對(duì)應(yīng)的
Class對(duì)象,這個(gè)
Class對(duì)象接著就會(huì)返回給調(diào)用者,
表示加載已經(jīng)完成。如果參數(shù)descriptor指定的類還沒有被加載過,這時(shí)候主要就是要看參數(shù)class_loader的值了。如果參數(shù)class_loader的值等于NULL,那么就需要調(diào)用DexFile類的靜態(tài)FindInClassPath來在系統(tǒng)啟動(dòng)類路徑尋找對(duì)應(yīng)的類。一旦尋找到,那么就會(huì)獲得包含目標(biāo)類的DEX文件,因此接下來就調(diào)用ClassLinker類的另外一個(gè)成員函數(shù)DefineClass從獲得的DEX文件中加載參數(shù)descriptor指定的類了。如果參數(shù)class_loader的值不等于NULL,也就是說ClassLinker類的成員函數(shù)FindClass的調(diào)用者指定了類加載器,那么就通過該類加載器來加載參數(shù)descriptor指定的類。每一個(gè)類加載器在Java層都對(duì)應(yīng)有一個(gè)java.lang.ClassLoader對(duì)象。通過調(diào)用這個(gè)類的成員函數(shù) loadClass即可加載指定的類。在我們這個(gè)場(chǎng)景中,上述的類是一個(gè)系統(tǒng)類加載器,它負(fù)責(zé)加載系統(tǒng)類。而我們當(dāng)前要加載的類為,它屬于一個(gè)系統(tǒng)類。系統(tǒng)類加載器在加載系統(tǒng)類實(shí)際上也是通過 JNI方法調(diào)用ClassLinker類的成員函數(shù)FindClass來實(shí)現(xiàn)的。只不過這時(shí)候傳進(jìn)來的參數(shù) class_loader是一個(gè) NULL 值。這樣,ClassLinker類的成員函數(shù) FindClass就會(huì)在系統(tǒng)啟動(dòng)類路徑中尋找參數(shù) descriptor指定的類可以在哪一個(gè) DEX文件加載,這是通過調(diào)用 DexFile類的靜態(tài)成員函數(shù) FindInClassPath來實(shí)現(xiàn)的。所謂的系統(tǒng)啟動(dòng)類路徑,其實(shí)就是一系列指定的由系統(tǒng)提供的DEX文件,這些DEX文件保存在ClassLinker類的成員變量boot_class_path_描述的一個(gè)向量中。那么問題就來了,這些DEX文件是怎么來的呢?我們知道,在ART運(yùn)行時(shí)中,我們使用的是OAT文件。如果看過前面這篇文章,就會(huì)很容易知道,OAT文件里面包含有DEX文件。而且ART運(yùn)行時(shí)在啟動(dòng)的時(shí)候,會(huì)加載一個(gè)名稱為system@framework@boot.art@classes.oat的OAT文件。這個(gè)OAT文件包含有多個(gè)DEX文件,每一個(gè)DEX文件都是一個(gè)系統(tǒng)啟動(dòng)類路徑,它們會(huì)被添加到ClassLinker類的成員變量boot_class_path_描述的向量中去。這里調(diào)用 DexFile 類的靜態(tài)成員函數(shù) FindInClassPath,實(shí)際要完成的工作就是從ClassLinker類的成員變量 boot_class_path_描述的一系列的 DEX文件中檢查哪一個(gè) DEX
文件包含有參數(shù) descriptor
指定的類。這可以通過解析
DEX
文件來實(shí)現(xiàn),關(guān)于
DEX
文件的格式,可以參考官方文檔:
。知道了參數(shù) descriptor指定的類定義在哪一個(gè) DEX 文件之后,就可以通過ClassLinker類的另外一個(gè)成員函數(shù) DefineClass來從中加載它了。接下來,我們就繼續(xù)分析ClassLinker類的成員函數(shù) DefineClass的實(shí)現(xiàn),如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片mirror::Class*ClassLinker::DefineClass(constchar*descriptor,mirror::ClassLoader*class_loader,constDexFile&dex_file,constDexFile::ClassDef&dex_class_def){Thread*self=Thread::Current();SirtRef<mirror::Class>klass(self,NULL);Loadtheclassfromthedexfile.if(UNLIKELY(!init_done_)){finishupinitofhandcraftedclass_roots_if(strcmp(descriptor,"Ljava/lang/Object;")==0){klass.reset(GetClassRoot(kJavaLangObject));}elseif(strcmp(descriptor,"Ljava/lang/Class;")==0){klass.reset(GetClassRoot(kJavaLangClass));}elseif(strcmp(descriptor,"Ljava/lang/String;")==0){klass.reset(GetClassRoot(kJavaLangString));}elseif(strcmp(descriptor,"Ljava/lang/DexCache;")==0){klass.reset(GetClassRoot(kJavaLangDexCache));}elseif(strcmp(descriptor,"Ljava/lang/reflect/ArtField;")==0){klass.reset(GetClassRoot(kJavaLangReflectArtField));}elseif(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);......{//Addthenewlyloadedclasstotheloadedclassestable.mirror::Class*existing=InsertClass(descriptor,klass.get(),Hash(descriptor));if(existing!=NULL){//Wefailedtoinsertbecauseweracedwithanotherthread.CallingEnsureResolvedmaycause//thisthreadtoblock.returnEnsureResolved(self,existing);}}......if(!LinkClass(klass,NULL,self)){//Linkingfailed.klass->SetStatus(mirror::Class::kStatusError,self);returnNULL;}......returnklass.get();}這個(gè)函數(shù)定義在文件
art/runtime/class_linker.cc
中。ClassLinker
類有一個(gè)類型為
bool
的成員變量
init_done_,用來表示
ClassLinker
是否已經(jīng)初始化完成。 ClassLinker在創(chuàng)建的時(shí)候,有一個(gè)初始化過程,用來創(chuàng)建一些內(nèi)部類。這些內(nèi)部類要么是手動(dòng)創(chuàng)建的,要么是從Image空間獲得的。關(guān)于ART虛擬機(jī)的Image空間,我們?cè)诤竺娣治鯝RT垃圾收集機(jī)制的文章中再詳細(xì)分析。調(diào)用ClassLinker類的成員函數(shù) DefineClass的時(shí)候,如果 ClassLinker正處于初始化過程,即其成員變量 init_done_的值等于 false,并且參數(shù) descriptor描述的是特定的內(nèi)部類,那么就將本地變量 klass指向它們,其余情況則會(huì)通過成員函數(shù) AllocClass為其分配存儲(chǔ)空間,以便后面通過成員函數(shù) LoadClass進(jìn)行初始化。ClassLinker類的成員函數(shù) LoadClass用來從指定的 DEX文件中加載指定的類。指定的類從DEX文件中加載完成后,需要通過另外一個(gè)成員函數(shù) InsertClass添加到ClassLinker的已加載類列表中去。 如果指定的類之前已經(jīng)加載過, 即調(diào)用成員函數(shù) InsertClass得到的返回值不等于空,那么就說明有另外的一個(gè)線程也正在加載指定的類。這時(shí)候就需要調(diào)用成員函數(shù)EnsureResolved來保證(等待)該類已經(jīng)加載并且解析完成。另一方面,如果沒有其它線程加載指定的類,那么當(dāng)前線程從指定的DEX文件加載完成指定的類后,還需要調(diào)用成員函數(shù)LinkClass來對(duì)加載后的類進(jìn)行解析。最后,一個(gè)類型為Class的對(duì)象就可以返回給調(diào)用者了,用來表示一個(gè)已經(jīng)加載和解析完成的類。接下來,我們主要分析
ClassLinker
類的成員函數(shù)
LoadClass
的實(shí)現(xiàn),以便可以了解類的加載過程。ClassLinker類的成員函數(shù) LoadClass的實(shí)現(xiàn)如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片voidClassLinker::LoadClass(constDexFile&dex_file,constDexFile::ClassDef&dex_class_def,SirtRef<mirror::Class>&klass,mirror::ClassLoader*class_loader){......klass->SetClassLoader(class_loader);......klass->SetDexClassDefIndex(dex_file.GetIndexForClassDef(dex_class_def));.....//Loadfieldsfields.constbyte*class_data=dex_file.GetClassData(dex_class_def);......ClassDataItemIteratorit(dex_file,class_data);Thread*self=Thread::Current();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_ti=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_ti=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<constOatFile::OatClass>oat_class;if(Runtime::Current()->IsStarted()&&!Runtime::Current()->UseCompileTimeClassPath()){oat_class.reset(GetOatClass(dex_file,klass->GetDexClassDefIndex()));}//Loadmethods.if(it.NumDirectMethods()!=0){//TODO:appenddirectmethodstoclassobjectmirror::ObjectArray<mirror::ArtMethod>*directs=AllocArtMethodArray(self,it.NumDirectMethods());......klass->SetDirectMethods(directs);}if(it.NumVirtualMethods()!=0){TODO:appenddirectmethodstoclassobjectmirror::ObjectArray<mirror::ArtMethod>*virtuals=AllocArtMethodArray(self,it.NumVirtualMethods());......klass->SetVirtualMethods(virtuals);}size_tclass_def_method_index=0;for(size_ti=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_ti=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++;}......}這個(gè)函數(shù)定義在文件 art/runtime/class_linker.cc中。我們首先要明確一下各個(gè)參數(shù)的含義:dex_file: 類型為DexFile,描述要加載的類所在的 DEX文件。dex_class_def:類型為ClassDef,描述要加載的類在 DEX文件里面的信息。klass:類型為Class,描述加載完成的類。class_loader: 類型為ClassLoader,描述所使用的類加載器??偟膩碚f,ClassLinker類的成員函數(shù)LoadClassdex_class_def、class_loader三個(gè)參數(shù)包含的相關(guān)信息設(shè)置到參數(shù)以便可以得到一個(gè)完整的已加載類信息。
的任務(wù)就是要用dex_file、klass描述的Class對(duì)象去,ClassLinker類的成員函數(shù) LoadClass主要完成的工作如下所示:將參數(shù)class_loader描述的ClassLoader設(shè)置到klass描述的Class對(duì)象中去,即給每一個(gè)已加載類關(guān)聯(lián)一個(gè)類加載器。通過DexFile類的成員函數(shù)GetIndexForClassDef獲得正在加載的類在DEX文件中的類索引號(hào),并且設(shè)置到klass描述的Class對(duì)象中去。這個(gè)類索引號(hào)是一個(gè)很重要的信息,因?yàn)槲覀冃枰ㄟ^類索引號(hào)在相應(yīng)的OAT文件找到一個(gè)OatClass結(jié)構(gòu)體。有了這個(gè)OatClass結(jié)構(gòu)體之后,我們才可以找到類方法對(duì)應(yīng)的本地機(jī)器指令。具體可以參考前面圖 1和一文。從參數(shù)dex_file描述的DEX文件中獲得正在加載的類的靜態(tài)成員變量和實(shí)例成員變量個(gè)數(shù),并且為每一個(gè)靜態(tài)成員變量和實(shí)例成員變量都分配一個(gè) ArtField對(duì)象,接著通過ClassLinker類的成員函數(shù)LoadField對(duì)這些ArtField對(duì)象進(jìn)行初始化。初始好得到的ArtField對(duì)象全部保存在 klass描述的Class對(duì)象中。4.調(diào)用ClassLinker類的成員函數(shù) GetOatClass,從相應(yīng)的 OAT文件中找到與正在加載的類對(duì)應(yīng)的一個(gè) OatClass結(jié)構(gòu)體oat_class。這需要利用到上面提到的 DEX類索引號(hào),這是因?yàn)镈EX類和OAT類根據(jù)索引號(hào)存在一一對(duì)應(yīng)關(guān)系。這一點(diǎn)可以參考圖 1和一文。從參數(shù)dex_file描述的DEX文件中獲得正在加載的類的直接成員函數(shù)和虛擬成員函數(shù)個(gè)數(shù),并且為每一個(gè)直接成員函數(shù)和虛擬成員函數(shù)都分配一個(gè) ArtMethod對(duì)象,接著通過ClassLinker類的成員函數(shù)LoadMethod對(duì)這些ArtMethod對(duì)象進(jìn)行初始化。初始好得到的ArtMethod對(duì)象全部保存在klass描述的Class對(duì)象中。每一個(gè)直接成員函數(shù)和虛擬成員函數(shù)都對(duì)應(yīng)有一個(gè)函數(shù)索引號(hào)。根據(jù)這個(gè)函數(shù)索引號(hào)可以在第4步得到的OatClass結(jié)構(gòu)體中找到對(duì)應(yīng)的本地機(jī)器指令,具體可以參考前面圖1和一文。所有與這些成員函數(shù)關(guān)聯(lián)的本地機(jī)器指令信息通過全局函數(shù) LinkCode設(shè)置到klass描述的Class對(duì)象中??偨Y(jié)來說,參數(shù) klass描述的Class對(duì)象包含了一系列的 ArtField對(duì)象和ArtMethod對(duì)象,其中,ArtField對(duì)象用來描述成員變量信息,而 ArtMethod用來描述成員函數(shù)信息。接下來,我們繼續(xù)分析全局函數(shù) LinkCode的實(shí)現(xiàn),以便可以了解如何在一個(gè) OAT文件中找到一個(gè) DEX類方法的本地機(jī)器指令。函數(shù)LinkCode的實(shí)現(xiàn)如下所示:[cpp]viewplaincopy 在CODE上查看代碼片派生到我的代碼片staticvoidLinkCode(SirtRef<mirror::ArtMethod>&method,constOatFile::OatClass*oat_class,uint32_tmethod_index)SHARED_LOCKS_REQUIRED(Locks::mutator_lock_){//Methodshouldn'thavealreadybeenlinked.DCHECK(method->GetEntryPointFromCompiledCode()==NULL);Everykindofmethodshouldatleastgetaninvokestubfromtheoat_method.non-abstractmethodsalsogettheircodepointers.constOatFile::OatMethodoat_method=oat_class->GetOatMethod(method_index);oat_method.LinkMethod(method.get());Installentrypointfrominterpreter.Runtime*runtime=Runtime::Current();boolenter_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()){Forstaticmethodsexcludingtheclassinitializer,installthetrampoline.ItwillbereplacedbytheproperentrypointbyClassLinker::FixupStaticTrampolinesafterinitializingclass(seeClassLinker::InitializeClassmethod).method->SetEntryPointFromCompiledCode(GetResolutionTrampoline(runtime->GetClassLinker()));}elseif(enter_interpreter){Setentrypointfromcompiledcodeifthere'snocodeorininterpreteronlymode.method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());}if(method->IsNative()){Unregisteringrestoresthedlsymlookupstub.method->UnregisterNative(Thread::Current());}//Allowinstrumentationitschancetohijackcode.runtime->GetInstrumentation()->UpdateMethodsCode(method.get(),method->GetEntryPointFromCompiledCode());}這個(gè)函數(shù)定義在文件art/runtime/class_linker.cc中。參數(shù)method表示要設(shè)置本地機(jī)器指令的類方法,參數(shù)
oat_class表示類方法 method在OAT文件中對(duì)應(yīng)的OatClass結(jié)構(gòu)體,參數(shù)method_index表示類方法method的索引號(hào)。通過參數(shù)method_index描述的索引號(hào)可以在 oat_class表示的OatClass結(jié)構(gòu)體中找到一個(gè)OatMethod結(jié)構(gòu)體oat_method。這個(gè)OatMethod結(jié)構(gòu)描述了類方法 method的本地機(jī)器指令相關(guān)信息,通過調(diào)用它的成員函數(shù)
LinkMethod
可以將這些信息設(shè)置到參數(shù) method描述的ArtMethod對(duì)象中去。如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片constvoid*OatFile::OatMethod::GetCode()const{returnGetOatPointer<constvoid*>(code_offset_);}......voidOatFile::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()); //UsedbynativemethodsinworkaroundJNImode.}中 的
這個(gè)函數(shù)定義在文件art/runtime/oat_file.cc中。其中,最重要的就是通過OatMethod類的成員函數(shù)字段,并且通過調(diào)用code_offset_
GetCode獲得OatMethod結(jié)構(gòu)體ArtMethod 類 的 成 員 函 數(shù)SetEntryPointFromCompiledCode設(shè)置到參數(shù)method描述的ArtMethod對(duì)象中去。OatMethod結(jié)構(gòu)體中的code_offset_字段指向的是一個(gè)本地機(jī)器指令函數(shù),這個(gè)本地機(jī)器指令函數(shù)正是通過翻譯參數(shù) method描述的類方法的 DEX字節(jié)碼得到的?;氐胶瘮?shù) LinkCode 中,它接著調(diào)用另外一個(gè)全局函數(shù) NeedsInterpreter檢查參數(shù)method描述的類方法是否需要通過解釋器執(zhí)行,它的實(shí)現(xiàn)如下所示:[cpp]viewplaincopy 在CODE上查看代碼片派生到我的代碼片//Returnstrueifthemethodmustrunwithinterpreter,falseotherwise.staticboolNeedsInterpreter(constmirror::ArtMethod*method,constvoid*code){if(code==NULL){//Nocode:needinterpreter.returntrue;}......Ifinterpretermodeisenabled,everymethod(exceptnativeandproxy)mustberunwithinterpreter.returnRuntime::Current()->GetInstrumentation()->InterpretOnly()&&!method->IsNative()&&!method->IsProxyMethod();}這個(gè)函數(shù)定義在文件 art/runtime/class_linker.cc中。在以下兩種情況下,一個(gè)類方法需要通過解釋器來執(zhí)行:1.沒有對(duì)應(yīng)的本地機(jī)器指令,即參數(shù) code的值等于 NULL。ART虛擬機(jī)運(yùn)行在解釋模式中,并且類方法不是JNI方法,并且也不是代理方法。調(diào)用Runtime類的靜態(tài)成員函數(shù) Current獲得的是描述 ART運(yùn)行時(shí)的一個(gè) Runtime對(duì)象。調(diào)用這個(gè) Runtime對(duì)象的成員函數(shù) GetInstrumentation獲得的是一個(gè) Instrumentation對(duì)象。這個(gè) Instrumentation 對(duì)象是用來調(diào)試 ART 運(yùn)行時(shí)的,通過調(diào)用它的成員函數(shù)InterpretOnly可以知道 ART虛擬機(jī)是否運(yùn)行在解釋模式中。中,JNI
因?yàn)镴NI方法是沒有對(duì)應(yīng)的 DEX字節(jié)碼的,因此即使 ART虛擬機(jī)運(yùn)行在解釋模式方法也不能通過解釋器來執(zhí)行。至于代理方法,由于是動(dòng)態(tài)生成的(沒有對(duì)應(yīng)的DEX
字節(jié)碼),因此即使
ART
虛擬機(jī)運(yùn)行在解釋模式中,它們也不通過解釋器來執(zhí)行(這一點(diǎn)猜測(cè)的,還沒有確認(rèn))
。回到函數(shù) LinkCode中,如果調(diào)用函數(shù) NeedsInterpreter得到的返回值 enter_interpreter等于true,那么就意味著參數(shù) method描述的類方法需要通過解釋器來執(zhí)行,這時(shí)候就將函數(shù)artInterpreterToInterpreterBridge設(shè)置為解釋器執(zhí)行該類方法的入口點(diǎn)。否則的話,就將另外一個(gè)函數(shù)artInterpreterToCompiledCodeBridge設(shè)置為解釋器執(zhí)行該類方法的入口點(diǎn)。為什么我們需要為類方法設(shè)置解釋器入口點(diǎn)呢?根據(jù)前面的分析可以知道, 在ART虛擬機(jī)中,并不是所有的類方法都是有對(duì)應(yīng)的本地機(jī)器指令的, 并且即使一個(gè)類方法有對(duì)應(yīng)的本地機(jī)器指令,當(dāng) ART虛擬機(jī)以解釋模式運(yùn)行時(shí),它也需要通過解釋器來執(zhí)行。當(dāng)以解釋器執(zhí)行的類方法在執(zhí)行的過程中調(diào)用了其它的類方法時(shí), 解釋器就需要進(jìn)一步知道被調(diào)用的類方法是應(yīng)用以解釋方式執(zhí)行, 還是本地機(jī)器指令方法執(zhí)行。 為了能夠進(jìn)行統(tǒng)一處理, 就給每一個(gè)類方法都設(shè)置一個(gè)解釋器入口點(diǎn)。 需要通過解釋執(zhí)行的類方法的解釋器入口點(diǎn)函數(shù)是artInterpreterToInterpreterBridge,它會(huì)繼續(xù)通過解釋器來執(zhí)行該類方法。需要通過本地機(jī)器指令執(zhí)行的類方法的解釋器入口點(diǎn)函數(shù)是artInterpreterToCompiledCodeBridge,它會(huì)間接地調(diào)用該類方法的本地機(jī)器指令。函數(shù)LinkCode繼續(xù)往下執(zhí)行,判斷參數(shù)method描述的類方法是否是一個(gè)抽象方法。抽象方法聲明類中是沒有實(shí)現(xiàn)的,必須要由子類實(shí)現(xiàn)。因此抽象方法在聲明類中是沒有對(duì)應(yīng)的本地機(jī)器指令的,它們必須要通過解釋器來執(zhí)行。不過,為了能夠進(jìn)行統(tǒng)一處理,我們?nèi)匀患傺b抽象方法有對(duì)應(yīng)的本地機(jī)器指令函數(shù),只不過這個(gè)本地機(jī)器指令函數(shù)被設(shè)置為GetCompiledCodeToInterpreterBridge。當(dāng)函數(shù)GetCompiledCodeToInterpreterBridge被調(diào)用時(shí),就會(huì)自動(dòng)進(jìn)入到解釋器中去。對(duì)于非抽象方法,函數(shù) LinkCode還要繼續(xù)往下處理。到這里有一點(diǎn)是需要注意的,前面通過調(diào)用 OatMethod類的成員函數(shù) LinkMethod,我們已經(jīng)設(shè)置好參數(shù) method描述的類方法的本地機(jī)器指令了。但是,在以下兩種情況下,我們需要進(jìn)行調(diào)整:1.當(dāng)參數(shù)method描述的類方法是一個(gè)非類靜態(tài)初始化函數(shù)(classinitializer)的靜態(tài)方法時(shí),我們不能直接執(zhí)行翻譯其DEX字節(jié)碼得到的本地機(jī)器指令。這是因?yàn)轭愳o態(tài)方法可以在不創(chuàng)建類對(duì)象的前提下執(zhí)行。這意味著一個(gè)類靜態(tài)方法在執(zhí)行的時(shí)候,對(duì)應(yīng)的類可能還沒有初始化好。這時(shí)候我們就需要先將對(duì)應(yīng)的類初始化好,再執(zhí)行相應(yīng)的靜態(tài)方法。為了能夠做到這一點(diǎn)。我們就先調(diào)用GetResolutionTrampoline函數(shù)得到一個(gè)Tampoline函數(shù),接著將這個(gè)Trampoline函數(shù)作為靜態(tài)方法的本地機(jī)器指令。這樣如果類靜態(tài)方法在對(duì)應(yīng)的類初始化前被調(diào)用,就會(huì)觸發(fā)上述的Trampoline函數(shù)被執(zhí)行。而當(dāng)上述Trampoline函數(shù)執(zhí)行時(shí),它們先初始化好對(duì)應(yīng)的類,再調(diào)用原來的類靜態(tài)方法對(duì)應(yīng)的本地機(jī)器指令。按照代碼中的注釋,當(dāng)一個(gè)類初始化完成之后,就可以調(diào)用函數(shù)ClassLinker::FixupStaticTrampolines來修復(fù)該類的靜態(tài)成員函數(shù)的本地機(jī)器指令,也是通過翻譯DEX字節(jié)碼得到的本地機(jī)器指令。這里需要注意的是,為什么類靜態(tài)初始化函數(shù)不需要按照其它的類靜態(tài)方法一樣設(shè)置Tampoline函數(shù)呢?這是因?yàn)轭愳o態(tài)初始化函數(shù)是一定保證是在類初始化過程中執(zhí)行的。當(dāng)參數(shù)method描述的類方法需要通過解釋器執(zhí)行時(shí),那么當(dāng)該類方法執(zhí)行時(shí),就不能執(zhí)行它的本地機(jī)器指令,因此我們就先調(diào)用GetCompiledCodeToInterpreterBridge函數(shù)獲得一個(gè)橋接函數(shù),并且將這個(gè)橋接函數(shù)假裝為類方法的本地機(jī)器指令。一旦該橋接函數(shù)被執(zhí)行,它就會(huì)入到解釋器去執(zhí)行類方法。通過這種方式,我們就可以以統(tǒng)一的方法來調(diào)用解釋執(zhí)行和本地機(jī)器指令執(zhí)行的類方法。函數(shù)LinkCode接下來繼續(xù)判斷參數(shù) method描述的類方法是否是一個(gè) JNI方法。如果是的話,那么就調(diào)用 ArtMethod 類的成員函數(shù) UnregisterNative來初始化它的 JNI方法調(diào)用接口。ArtMethod類的成員函數(shù) UnregisterNative的實(shí)現(xiàn)如下所示:[cpp]viewplaincopy在CODE上查看代碼片派生到我的代碼片voidArtMethod::UnregisterNative(Thread*self){CHECK(IsNative())<<PrettyMethod(this);restorestubtolookupnativepointerviadlsymRegisterNative(self,GetJniDlsymLookupStub());}這個(gè)函數(shù)定義在文件 runtime/mirror/art_method.cc 中。ArtMethod類的成員函數(shù) UnregisterNative實(shí)際上就是將一個(gè) JNI方法的初始化入口設(shè)置為通過調(diào)用函數(shù) GetJniDlsymLookupStub 獲得的一個(gè) Stub。這個(gè) Stub的作用是,當(dāng)一個(gè)JNI方法被調(diào)用時(shí),如果還沒有顯示地注冊(cè)有 Native函數(shù),那么它就會(huì)自動(dòng)從已加載的SO文件查找是否存在一個(gè)對(duì)應(yīng)的 Native函數(shù)。如果存在的話,就將它注冊(cè)為 JNI方法的Native函數(shù),并且執(zhí)行它。這就是隱式的 JNI方法注冊(cè)。回到函數(shù) LinkCode,它最后調(diào)用 Instrumentation類的成員函數(shù) UpdateMethodsCode檢查是否要進(jìn)一步修改參數(shù) method描述的類方法的本地機(jī)器指令入口, 它的實(shí)現(xiàn)如下所示:[cpp]viewplaincopy 在CODE上查看代碼片派生到我的代碼片voidInstrumentation::UpdateMethodsCode(mirror::ArtMethod*method,constvoid*code)const{if(LIKELY(!instrumentation_stubs_installed_)){method->SetEntryPointFromCompiledCode(code);}else{if(!interpreter_stubs_installed_||method->IsNative()){method->SetEntryPointFromCompiledCode(GetQuickInstrumentationEntryPoint());}else{method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());}}}這個(gè)函數(shù)定義在文件 art/runtime/instrumentation.cc 中。Instrumentation類是用來調(diào)用 ART運(yùn)行時(shí)的。例如,當(dāng)我們需要監(jiān)控類方法的調(diào)用時(shí),就可以往 Instrumentation注冊(cè)一些 Listener。這樣當(dāng)類方法調(diào)用時(shí),這些注冊(cè)的 Listener就會(huì)得到回調(diào)。當(dāng) Instrumentation 注冊(cè)有相應(yīng)的 Listener 時(shí),它的成員變量instrumentation_stubs_installed_的值就會(huì)等于
true。因此,當(dāng)Instrumentation類的成員變量 instrumentation_stubs_installed_的值等于 true時(shí),我們需要使用一個(gè)監(jiān)控函數(shù)來替換掉類方法原來的本地機(jī)器指令。 這樣當(dāng)類方法被調(diào)用時(shí),監(jiān)控函數(shù)就獲得控制權(quán),它可以在調(diào)用原來的本地機(jī)器指令前后,向注冊(cè)的 Listener發(fā)出通知。對(duì)于JNI方法,我們通過調(diào)用函數(shù) GetQuickInstrumentationEntryPoint 獲得的函數(shù)作為其監(jiān)控函數(shù);而對(duì)其它的類方法,我們通過調(diào)用函數(shù) GetCompiledCodeToInterpreterBridge獲得的函數(shù)作為其監(jiān)控函數(shù)。另一方面,如果沒有 Listener 注冊(cè)到 Instrumentation 中,即它的成員變量instrumentation_stubs_installed_的值等于 false,那么 Instrumentation 類的成員函UpdateMethodsCode就會(huì)使用參數(shù) code描述的本地機(jī)器指令作為參數(shù) method描述的類方法的本地機(jī)器指令入口。 參數(shù)code描述的本地機(jī)器指一般就是翻譯類方法的 DEX字節(jié)碼得到的本地機(jī)器指令了。實(shí)際上是相當(dāng)于沒有修改類方法的本地機(jī)器指令入口。這樣,一個(gè)類的加載過程就完成了。加載完成后,得到的是一個(gè) Class對(duì)象。這個(gè)Class對(duì)象關(guān)聯(lián)有一系列的 ArtField對(duì)象和ArtMethod對(duì)象。其中,ArtField對(duì)象描述的是成員變量,而ArtMethod對(duì)象描述的是成員函數(shù)。對(duì)于每一個(gè)ArtMethod對(duì)象,它都有一個(gè)解釋器入口點(diǎn)和一個(gè)本地機(jī)器指令入口點(diǎn)。這樣,無論一個(gè)類方法是通過解釋器執(zhí)行,還是直接以本地機(jī)器指令執(zhí)行,我們都可以以統(tǒng)一的方式來進(jìn)行調(diào)用。同時(shí),理解了上述的類加載過程后,我們就可以知道,我們?cè)贜ative層通過JNI接口FindClass查找或者加載類時(shí),得到的一個(gè)不透明的jclass值,實(shí)際上
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025員工內(nèi)部安全責(zé)任合同
- 2025商品房購銷合同協(xié)議
- 2025墻紙施工合同
- 2025年北京寫字樓租賃合同模板下載
- 2025高級(jí)鋼材銷售合同
- 2025建筑公司流動(dòng)資金借款的合同
- 2025化工產(chǎn)品購銷合同主要條款
- 2025授權(quán)借款合同
- 患者溝通手冊(cè)與指導(dǎo)教程
- 季度辦公室運(yùn)營(yíng)情況分析總結(jié)報(bào)告
- 《腕管綜合征》課件
- 施工方案編制要求做到
- YY/T 0109-2024醫(yī)用超聲霧化器
- 2024年涉密人員考試試題庫保密基本知識(shí)試題含答案
- 2024年退股事宜洽談備忘錄3篇
- 2025版科技成果轉(zhuǎn)化合作協(xié)議書3篇
- 微創(chuàng)介入診斷治療管理制度
- 新質(zhì)生產(chǎn)力促進(jìn)老年人公共體育服務(wù)高質(zhì)量發(fā)展研究
- 大學(xué)生學(xué)業(yè)個(gè)人規(guī)劃
- 軟件產(chǎn)品售后服務(wù)及維護(hù)流程指南
- T-ZNZ 248-2024 紅黃壤貧瘠耕地快速培肥技術(shù)規(guī)范
評(píng)論
0/150
提交評(píng)論