Android运行时ART执行类方法的过程分析
在前面一篇文章中,我们分析了ART运行时加载类以及查找其方法的过程。一旦找到了目标类方法,我们就可以获得它的DEX字节码或者本地机器指令,这样就可以对它进行执行了。在ART运行时中,类方法的执行方式有两种。一种是像Dalvik虚拟机一样,将其DEX字节码交给解释器执行;另一种则是直接将其本地机器指令交给CPU执行。在本文中,我们就将通过分析ART运行时执行类方法的过程来理解ART运行时的运行原理。
我们先来看看图1,它描述了ART运行时执行一个类方法的流程,如下所示:
图1综合了我们在前面和这两篇文章中提到的两个知识点,我们先来回顾一下。
第一个知识点是ART运行时将DEX字节码翻译成本地机器指令时,使用的后端(Backend)是Quick类型还是Portable类型。ART运行时在编译的时候,默认使用的后端是Quick类型的,不过可以通过将环境变量ART_USE_PORTABLE_COMPILER的值设置为true来指定使用Portable类型的后端,如下所示:
[plain]viewplaincopy在CODE上查看代码片派生到我的代码片
......
LIBART_CFLAGS:=
ifeq($(ART_USE_PORTABLE_COMPILER),true)
LIBART_CFLAGS+=-DART_USE_PORTABLE_COMPILER=1
endif
......
上述编译脚本定义在文件art/runtime/Android.mk中。
一旦我们将环境变量ART_USE_PORTABLE_COMPILER的值设置为true,那么就会定义一个宏ART_USE_PORTABLE_COMPILER。参考前面这篇文章,宏ART_USE_PORTABLE_COMPILER的定义与否会影响到加载OAT文件所使用的方法,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
OatFileOatFile::Open(conststd::string&filename,
conststd::string&location,
byterequested_base,
boolexecutable){
CHECK(!filename.empty())< CheckLocation(filename);
#ifdefART_USE_PORTABLE_COMPILER
//IfweareusingPORTABLE,usedlopentodealwithrelocations.
//
//WeuseourownELFloaderforQuicktodealwithlegacyappsthat
//openagenerateddexfilebyname,removethefile,thenopen
//anothergenerateddexfilewiththesamename.http://b/10614658
if(executable){
returnOpenDlopen(filename,location,requested_base);
}
#endif
//Ifwearen''ttryingtoexecute,wejustuseourownElfFileloaderforacouplereasons:
//
//Ontarget,dlopenmayfailwhencompilingduetoselinuxrestrictionsoninstalld.
//
//Onhost,dlopenisexpectedtofailwhencrosscompiling,sofallbacktoOpenElfFile.
//Thiswon''tworkforportableruntimeexecutionbecauseitdoesn''tprocessrelocations.
UniquePtrfile(OS::OpenFileForReading(filename.c_str()));
if(file.get()==NULL){
returnNULL;
}
returnOpenElfFile(file.get(),location,requested_base,false,executable);
}
这个函数定义在文件art/runtime/oat_file.cc中。
这个函数的详细解释可以参考前面一文,这里只对结论进行进一步的解释。从注释可以知道,通过Portable后端和Quick后端生成的OAT文件的本质区别在于,前者使用标准的动态链接器加载,而后者使用自定义的加载器加载。
标准动态链接器在加载SO文件(这里是OAT文件)的时候,会自动处理重定位问题。也就是说,在生成的本地机器指令中,如果有依赖其它的SO导出的函数,那么标准动态链接器就会将被依赖的SO也加载进来,并且从里面找到被引用的函数的地址,用来重定位引用了该函数的符号。生成的本地机器指令引用的一般都是些SO呢?其实就是ART运行时库(libart.so)。例如,如果在生成的本地机器指令需要分配一个对象,那么就需要调用ART运行时的堆管理器提供的AllocObject接口来分配。
自定义加载器的做法就不一样了。它在加载OAT文件时,并不需要做上述的重定位操作。因为Quick后端生成的本地机器指令需要调用一些外部库提供的函数时,是通过一个函数跳转表来实现的。由于在加载过程中不需要执行重定位,因此加载过程就会更快,Quick的名字就是这样得来的。Portable后端生成的本地机器指令在调用外部库提供的函数时,使用了标准的方法,因此它不但可以在ART运行时加载,也可以在其它运行时加载,因此就得名于Portable。
接下来我们的重点是分析Quick后端生成的本地机器指令在调用外部库函数时所使用的函数跳转表是如何定义的。
在前面分析Dalvik虚拟机的文章中,我们提到每一个Dalvik虚拟机线程在内部都通过一个Thread对象描述。这个Thread对象包含了一些与虚拟机相关的信息。例如,JNI函数调用函数表。在ART运行时中创建的线程,和Davik虚拟机线程一样,在内部也会通过一个Thread对象来描述。这个新的Thread对象内部除了定义JNI调用函数表之外,还定义了我们在上面提到的外部函数调用跳转表。
在前面一文,我们提到了ART运行时的启动和初始化过程。其中的一个初始化过程便是将主线程关联到ART运行时去,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
boolRuntime::Init(constOptions&raw_options,boolignore_unrecognized){
......
java_vm_=newJavaVMExt(this,options.get());
......
Threadself=Thread::Attach("main",false,NULL,false);
......
returntrue;
}
这个函数定义在文件art/runtime/runtime.cc中。
在Runtime类的成员函数Init中,通过调用Thread类的静态成员函数Attach将当前线程,也就是主线程,关联到ART运行时去。在关联的过程中,就会初始化一个外部库函数调用跳转表。
Thread类的静态成员函数Attach的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
ThreadThread::Attach(constcharthread_name,boolas_daemon,jobjectthread_group,
boolcreate_peer){
Threadself;
Runtimeruntime=Runtime::Current();
......
{
MutexLockmu(NULL,Locks::runtime_shutdown_lock_);
if(runtime->IsShuttingDown()){
LOG(ERROR)<<"Threadattachingwhileruntimeisshuttingdown:"< returnNULL;
}else{
Runtime::Current()->StartThreadBirth();
self=newThread(as_daemon);
self->Init(runtime->GetThreadList(),runtime->GetJavaVM());
Runtime::Current()->EndThreadBirth();
}
}
......
returnself;
}
这个函数定义在文件art/runtime/thread.cc中。
在Thread类的静态成员函数Attach中,最重要的就是创建了一个Thread对象来描述当前被关联到ART运行时的线程。创建了这个Thread对象之后,马上就调用它的成员函数Init来对它进行初始化。
Thread类的成员函数Init的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidThread::Init(ThreadListthread_list,JavaVMExtjava_vm){
......
InitTlsEntryPoints();
......
jni_env_=newJNIEnvExt(this,java_vm);
......
}
这个函数定义在文件art/runtime/thread.cc中。
Thread类的成员函数Init除了给当前的线程创建一个JNIEnvExt对象来描述它的JNI调用接口之外,还通过调用另外一个成员函数InitTlsEntryPoints来初始化一个外部库函数调用跳转表。
Thread类的成员函数InitTlsEntryPoints的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidInitEntryPoints(InterpreterEntryPointsipoints,JniEntryPointsjpoints,
PortableEntryPointsppoints,QuickEntryPointsqpoints);
voidThread::InitTlsEntryPoints(){
......
InitEntryPoints(&interpreter_entrypoints_,&jni_entrypoints_,&portable_entrypoints_,
&quick_entrypoints_);
}
这个函数定义在文件art/runtime/thread.cc中。
Thread类定义了四个成员变量interpreter_entrypoints_、jni_entrypoints_、portable_entrypoints_和quick_entrypoints_,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
classPACKED(4)Thread{
......
public:
//Entrypointfunctionpointers
//TODO:movethisnearthetop,sincechangingitsoffsetrequiresalloatstoberecompiled!
InterpreterEntryPointsinterpreter_entrypoints_;
JniEntryPointsjni_entrypoints_;
PortableEntryPointsportable_entrypoints_;
QuickEntryPointsquick_entrypoints_;
......
};
Thread类的声明定义在文件art/runtime/thread.h中。
Thread类将外部库函数调用跳转表划分为4个,其中,interpreter_entrypoints_描述的是解释器要用到的跳转表,jni_entrypoints_描述的是JNI调用相关的跳转表,portable_entrypoints_描述的是Portable后端生成的本地机器指令要用到的跳转表,而quick_entrypoints_描述的是Quick后端生成的本地机器指令要用到的跳转表。从这里可以看出,Portable后端生成的本地机器指令也会使用到ART运行时内部的函数跳转表。不过与Quick后端生成的本地机器指令使用到的ART运行时内部的函数跳转表相比,它里面包含的函数项会少很多很多。接下来我们将会看到这一点。
回到Thread类的成员函数InitTlsEntryPoints中,它通过调用一个全局函数InitEntryPoints来初始化上述的4个跳转表。全局函数InitEntryPoints的实现是和CPU体系结构相关的,因为跳转表里面的函数调用入口是用汇编语言来实现的。
我们以ARM体系架构为例,来看全局函数InitEntryPoints的实现,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidInitEntryPoints(InterpreterEntryPointsipoints,JniEntryPointsjpoints,
PortableEntryPointsppoints,QuickEntryPointsqpoints){
//Interpreter
ipoints->pInterpreterToInterpreterBridge=artInterpreterToInterpreterBridge;
ipoints->pInterpreterToCompiledCodeBridge=artInterpreterToCompiledCodeBridge;
//JNI
jpoints->pDlsymLookup=art_jni_dlsym_lookup_stub;
//Portable
ppoints->pPortableResolutionTrampoline=art_portable_resolution_trampoline;
ppoints->pPortableToInterpreterBridge=art_portable_to_interpreter_bridge;
//Alloc
qpoints->pAllocArray=art_quick_alloc_array;
qpoints->pAllocArrayWithAccessCheck=art_quick_alloc_array_with_access_check;
qpoints->pAllocObject=art_quick_alloc_object;
qpoints->pAllocObjectWithAccessCheck=art_quick_alloc_object_with_access_check;
qpoints->pCheckAndAllocArray=art_quick_check_and_alloc_array;
qpoints->pCheckAndAllocArrayWithAccessCheck=art_quick_check_and_alloc_array_with_access_check;
//Cast
qpoints->pInstanceofNonTrivial=artIsAssignableFromCode;
qpoints->pCanPutArrayElement=art_quick_can_put_array_element;
qpoints->pCheckCast=art_quick_check_cast;
//DexCache
qpoints->pInitializeStaticStorage=art_quick_initialize_static_storage;
qpoints->pInitializeTypeAndVerifyAccess=art_quick_initialize_type_and_verify_access;
qpoints->pInitializeType=art_quick_initialize_type;
qpoints->pResolveString=art_quick_resolve_string;
//Field
qpoints->pSet32Instance=art_quick_set32_instance;
qpoints->pSet32Static=art_quick_set32_static;
qpoints->pSet64Instance=art_quick_set64_instance;
qpoints->pSet64Static=art_quick_set64_static;
qpoints->pSetObjInstance=art_quick_set_obj_instance;
qpoints->pSetObjStatic=art_quick_set_obj_static;
qpoints->pGet32Instance=art_quick_get32_instance;
qpoints->pGet64Instance=art_quick_get64_instance;
qpoints->pGetObjInstance=art_quick_get_obj_instance;
qpoints->pGet32Static=art_quick_get32_static;
qpoints->pGet64Static=art_quick_get64_static;
qpoints->pGetObjStatic=art_quick_get_obj_static;
//FillArray
qpoints->pHandleFillArrayData=art_quick_handle_fill_data;
//JNI
qpoints->pJniMethodStart=JniMethodStart;
qpoints->pJniMethodStartSynchronized=JniMethodStartSynchronized;
qpoints->pJniMethodEnd=JniMethodEnd;
qpoints->pJniMethodEndSynchronized=JniMethodEndSynchronized;
qpoints->pJniMethodEndWithReference=JniMethodEndWithReference;
qpoints->pJniMethodEndWithReferenceSynchronized=JniMethodEndWithReferenceSynchronized;
//Locks
qpoints->pLockObject=art_quick_lock_object;
qpoints->pUnlockObject=art_quick_unlock_object;
//Math
qpoints->pCmpgDouble=CmpgDouble;
qpoints->pCmpgFloat=CmpgFloat;
qpoints->pCmplDouble=CmplDouble;
qpoints->pCmplFloat=CmplFloat;
qpoints->pFmod=fmod;
qpoints->pSqrt=sqrt;
qpoints->pL2d=__aeabi_l2d;
qpoints->pFmodf=fmodf;
qpoints->pL2f=__aeabi_l2f;
qpoints->pD2iz=__aeabi_d2iz;
qpoints->pF2iz=__aeabi_f2iz;
qpoints->pIdivmod=__aeabi_idivmod;
qpoints->pD2l=art_d2l;
qpoints->pF2l=art_f2l;
qpoints->pLdiv=__aeabi_ldivmod;
qpoints->pLdivmod=__aeabi_ldivmod;//resultreturnedinr2:r3
qpoints->pLmul=art_quick_mul_long;
qpoints->pShlLong=art_quick_shl_long;
qpoints->pShrLong=art_quick_shr_long;
qpoints->pUshrLong=art_quick_ushr_long;
//Intrinsics
qpoints->pIndexOf=art_quick_indexof;
qpoints->pMemcmp16=__memcmp16;
qpoints->pStringCompareTo=art_quick_string_compareto;
qpoints->pMemcpy=memcpy;
//Invocation
qpoints->pQuickResolutionTrampoline=art_quick_resolution_trampoline;
qpoints->pQuickToInterpreterBridge=art_quick_to_interpreter_bridge;
qpoints->pInvokeDirectTrampolineWithAccessCheck=art_quick_invoke_direct_trampoline_with_access_check;
qpoints->pInvokeInterfaceTrampoline=art_quick_invoke_interface_trampoline;
qpoints->pInvokeInterfaceTrampolineWithAccessCheck=art_quick_invoke_interface_trampoline_with_access_check;
qpoints->pInvokeStaticTrampolineWithAccessCheck=art_quick_invoke_static_trampoline_with_access_check;
qpoints->pInvokeSuperTrampolineWithAccessCheck=art_quick_invoke_super_trampoline_with_access_check;
qpoints->pInvokeVirtualTrampolineWithAccessCheck=art_quick_invoke_virtual_trampoline_with_access_check;
//Thread
qpoints->pCheckSuspend=CheckSuspendFromCode;
qpoints->pTestSuspend=art_quick_test_suspend;
//Throws
qpoints->pDeliverException=art_quick_deliver_exception;
qpoints->pThrowArrayBounds=art_quick_throw_array_bounds;
qpoints->pThrowDivZero=art_quick_throw_div_zero;
qpoints->pThrowNoSuchMethod=art_quick_throw_no_such_method;
qpoints->pThrowNullPointer=art_quick_throw_null_pointer_exception;
qpoints->pThrowStackOverflow=art_quick_throw_stack_overflow;
};
这个函数定义在文件art/runtime/arch/arm/entrypoints_init_arm.cc中。
从函数InitEntryPoints的实现就可以看到Quick后端和Portable后端生成的本地机器指令要使用到的外部库函数调用跳转表的初始化过程了。例如,如果在生成的本地机器指令中,需要调用一个JNI函数,那么就需要通过art_jni_dlsym_lookup_stub函数来间接地调用,以便可以找到正确的JNI函数来调用。
此外,我们还可以看到,解释器要用到的跳转表只包含了两项,分别是artInterpreterToInterpreterBridge和artInterpreterToCompiledCodeBridge。前者用来从一个解释执行的类方法跳到另外一个也是解释执行的类方法去执行,后者用来从一个解释执行的类方法跳到另外一个以本地机器指令执行的类方法去执行。
Portable后端生成的本地机器指令要用到的跳转表也只包含了两项,分别是art_portable_resolution_trampoline和art_portable_to_interpreter_bridge。前者用作一个还未链接好的类方法的调用入口点,后者用来从一个以本地机器指令执行的类方法跳到另外一个解释执行的类方法去执行。
剩下的其它代码均是用来初始化Quick后端生成的本地机器指令要用到的跳转表,它包含的项非常多,但是可以划分为Alloc(对象分配)、Cast(类型转换)、DexCache(Dex缓访问)、Field(成员变量访问)、FillArray(数组填充)、JNI(JNI函数调用)、Locks(锁)、Math(数学计算)、Intrinsics(内建函数调用)、Invocation(类方法调用)、Thread(线程操作)和Throws(异常处理)等12类。
有了这些跳转表之后,当我们需要在生成的本地机器指令中调用一个外部库提供的函数时,只要找到用来描述当前线程的Thread对象,然后再根据上述的四个跳转表在该Thread对象内的偏移位置,那么就很容易找到所需要的跳转项了。
以上就是我们需要了解的第一个知识点,也就是Portable后端和Quick后端生成的本地机器指令的区别。接下来我们现来看另外一个知识点,它们是涉及到类方法的执行方式,也就是是通过解释器来执行,还是直接以本地机器指令来执行,以及它们之间是如何穿插执行的。
在前面这篇文章中,我们提到,在类的加载过程中,需要对类的各个方法进行链接,实际上就是确定它们是通过解释器来执行,还是以本地机器指令来直接执行,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
staticvoidLinkCode(SirtRef&method,constOatFile::OatClassoat_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.
Runtimeruntime=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::FixupStaticTrampolines
//afterinitializingclass(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());
}
这个函数定义在文件art/runtime/class_linker.cc中。
函数LinkCode的详细解释可以参考前面一文,这里我们只对结论进行总结,以及对结论进行进一步的分析:
1.ART运行时有两种执行方法:解释执行模式和本地机器指令执行模式。默认是本地机器指令执行模式,但是在启动ART运行时时可以通过-Xint选项指定为解释执行模式。
2.即使是在本地机器指令模式中,也有类方法可能需要以解释模式执行。反之亦然。解释执行的类方法通过函数artInterpreterToCompiledCodeBridge的返回值调用本地机器指令执行的类方法;本地机器指令执行的类方法通过函数GetCompiledCodeToInterpreterBridge的返回值调用解释执行的类方法;解释执行的类方法通过函数artInterpreterToInterpreterBridge的返回值解释执行的类方法。
3.在解释执行模式下,除了JNI方法和动态Proxy方法,其余所有的方法均通过解释器执行,它们的入口点设置为函数GetCompiledCodeToInterpreterBridge的返回值。
4.抽象方法不能执行,它必须要由子类实现,因此会将抽象方法的入口点设置为函数GetCompiledCodeToInterpreterBridge的返回值,目的检测是否在本地机器指令中调用了抽象方法。如果调用了,上述入口点就会抛出一个异常。
5.静态类方法的执行模式延迟至类初始化确定。在类初始化之前,它们的入口点由函数GetResolutionTrampoline的返回值代理。
接下来,我们就着重分析artInterpreterToCompiledCodeBridge、GetCompiledCodeToInterpreterBridge、artInterpreterToInterpreterBridge和GetResolutionTrampoline这4个函数以及它们所返回的函数的实现,以便可以更好地理解上述5个结论。
函数artInterpreterToCompiledCodeBridge用来在解释器中调用以本地机器指令执行的函数,它的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
extern"C"voidartInterpreterToCompiledCodeBridge(Threadself,MethodHelper&mh,
constDexFile::CodeItemcode_item,
ShadowFrameshadow_frame,JValueresult){
mirror::ArtMethodmethod=shadow_frame->GetMethod();
//Ensurestaticmethodsareinitialized.
if(method->IsStatic()){
Runtime::Current()->GetClassLinker()->EnsureInitialized(method->GetDeclaringClass(),true,true);
}
uint16_targ_offset=(code_item==NULL)?0:code_item->registers_size_-code_item->ins_size_;
#ifdefined(ART_USE_PORTABLE_COMPILER)
ArgArrayarg_array(mh.GetShorty(),mh.GetShortyLength());
arg_array.BuildArgArrayFromFrame(shadow_frame,arg_offset);
method->Invoke(self,arg_array.GetArray(),arg_array.GetNumBytes(),result,mh.GetShorty()[0]);
#else
method->Invoke(self,shadow_frame->GetVRegArgs(arg_offset),
(shadow_frame->NumberOfVRegs()-arg_offset)4,
result,mh.GetShorty()[0]);
#endif
}
这个函数定义在文件art/runtime/entrypoints/interpreter/interpreter_entrypoints.cc中。
被调用的类方法通过一个ArtMethod对象来描述,并且可以在调用栈帧shadow_frame中获得。获得了用来描述被调用方法的ArtMehtod对象之后,就可以调用它的成员函数Invoke来对它进行执行。后面我们就会看到,ArtMethod类的成员函数Invoke会找到类方法的本地机器指令来执行。
在调用类方法的本地机器指令的时候,从解释器调用栈获取的传入参数根据ART运行时使用的是Quick后端还是Portable后端来生成本地机器指令有所不同。不过最终都会ArtMethod类的成员函数Invoke来执行被调用类方法的本地机器指令。
函数GetCompiledCodeToInterpreterBridge用来返回一个函数指针,这个函数指针指向的函数用来从以本地机器指令执行的类方法中调用以解释执行的类方法,它的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
staticinlineconstvoidGetCompiledCodeToInterpreterBridge(){
#ifdefined(ART_USE_PORTABLE_COMPILER)
returnGetPortableToInterpreterBridge();
#else
returnGetQuickToInterpreterBridge();
#endif
}
这个函数定义在文件art/runtime/entrypoints/entrypoint_utils.h中。
根据ART运行时使用的是Quick后端还是Portable后端,函数GetCompiledCodeToInterpreterBridge的返回值有所不同,不过它们的作用是一样的。我们假设ART运行时使用的是Quick后端,那么函数GetCompiledCodeToInterpreterBridge的返回值通过调用函数GetQuickToInterpreterBridge来获得。
函数GetQuickToInterpreterBridge的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
extern"C"voidart_quick_to_interpreter_bridge(mirror::ArtMethod);
staticinlineconstvoidGetQuickToInterpreterBridge(){
returnreinterpret_cast(art_quick_to_interpreter_bridge);
}
这个函数定义在文件art/runtime/entrypoints/entrypoint_utils.h中。
函数GetQuickToInterpreterBridge的返回值实际上指向的是函数art_quick_to_interpreter_bridge。函数art_quick_to_interpreter_bridge是使用汇编代码来实现的,用来从本地机器指令进入到解释器的。
以ARM体系结构为例,函数art_quick_to_interpreter_bridge的实现如下所示:
[plain]viewplaincopy在CODE上查看代码片派生到我的代码片
.externartQuickToInterpreterBridge
ENTRYart_quick_to_interpreter_bridge
SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME
movr1,r9@passThread::Current
movr2,sp@passSP
blxartQuickToInterpreterBridge@(Methodmethod,Thread,SP)
ldrr2,[r9,#THREAD_EXCEPTION_OFFSET]@loadThread::Current()->exception_
ldrlr,[sp,#44]@restorelr
addsp,#48@popframe
.cfi_adjust_cfa_offset-48
cbnzr2,1f@successifnoexceptionispending
bxlr@returnonsuccess
1:
DELIVER_PENDING_EXCEPTION
ENDart_quick_to_interpreter_bridge
这个函数定义在文件art/runtime/arch/arm/quick_entrypoints_arm.S中。
很明显,函数art_quick_to_interpreter_bridge通过调用另外一个函数artQuickToInterpreterBridge从本地机器指令进入到解释器中去。
函数artQuickToInterpreterBridge的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
extern"C"uint64_tartQuickToInterpreterBridge(mirror::ArtMethodmethod,Threadself,
mirror::ArtMethodsp)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_){
//Ensurewedon''tgetthreadsuspensionuntiltheobjectargumentsaresafelyintheshadow
//frame.
FinishCalleeSaveFrameSetup(self,sp,Runtime::kRefsAndArgs);
if(method->IsAbstract()){
ThrowAbstractMethodError(method);
return0;
}else{
constcharold_cause=self->StartAssertNoThreadSuspension("Buildinginterpretershadowframe");
MethodHelpermh(method);
constDexFile::CodeItemcode_item=mh.GetCodeItem();
uint16_tnum_regs=code_item->registers_size_;
voidmemory=alloca(ShadowFrame::ComputeSize(num_regs));
ShadowFrameshadow_frame(ShadowFrame::Create(num_regs,NULL,//Nolastshadowcomingfromquick.
method,0,memory));
size_tfirst_arg_reg=code_item->registers_size_-code_item->ins_size_;
BuildQuickShadowFrameVisitorshadow_frame_builder(sp,mh.IsStatic(),mh.GetShorty(),
mh.GetShortyLength(),
shadow_frame,first_arg_reg);
shadow_frame_builder.VisitArguments();
//Pushatransitionbackintomanagedcodeontothelinkedlistinthread.
ManagedStackfragment;
self->PushManagedStackFragment(&fragment);
self->PushShadowFrame(shadow_frame);
self->EndAssertNoThreadSuspension(old_cause);
if(method->IsStatic()&&!method->GetDeclaringClass()->IsInitializing()){
//Ensurestaticmethod''sclassisinitialized.
if(!Runtime::Current()->GetClassLinker()->EnsureInitialized(method->GetDeclaringClass(),
true,true)){
DCHECK(Thread::Current()->IsExceptionPending());
self->PopManagedStackFragment(fragment);
return0;
}
}
JValueresult=interpreter::EnterInterpreterFromStub(self,mh,code_item,shadow_frame);
//Poptransition.
self->PopManagedStackFragment(fragment);
returnresult.GetJ();
}
}
这个函数定义在文件art/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc中。
函数artQuickToInterpreterBridge的作用实际上就是找到被调用类方法method的DEX字节码code_item,然后根据调用传入的参数构造一个解释器调用栈帧shadow_frame,最后就可以通过函数interpreter::EnterInterpreterFromStub进入到解释器去执行了。
既然已经知道了要执行的类方法的DEX字节码,以及已经构造好了要执行的类方法的调用栈帧,我们就不难理解解释器是如何执行该类方法了,具体可以参考一下这篇文章描述的Dalvik虚拟机解释器的实现。
如果要执行的类方法method是一个静态方法,那么我们就需要确保它的声明类是已经初始化过了的。如果还没有初始化过,那么就需要调用ClassLinker类的成员函数EnsureInitialized来对它进行初始化。
函数artInterpreterToInterpreterBridge用来从解释执行的函数调用到另外一个也是解释执行的函数,它的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
extern"C"voidartInterpreterToInterpreterBridge(Threadself,MethodHelper&mh,
constDexFile::CodeItemcode_item,
ShadowFrameshadow_frame,JValueresult){
if(UNLIKELY(__builtin_frame_address(0)GetStackEnd())){
ThrowStackOverflowError(self);
return;
}
ArtMethodmethod=shadow_frame->GetMethod();
if(method->IsStatic()&&!method->GetDeclaringClass()->IsInitializing()){
if(!Runtime::Current()->GetClassLinker()->EnsureInitialized(method->GetDeclaringClass(),
true,true)){
DCHECK(Thread::Current()->IsExceptionPending());
return;
}
CHECK(method->GetDeclaringClass()->IsInitializing());
}
self->PushShadowFrame(shadow_frame);
if(LIKELY(!method->IsNative())){
result->SetJ(Execute(self,mh,code_item,shadow_frame,JValue()).GetJ());
}else{
//Wedon''texpecttobeaskedtointerpretnativecode(whichisenteredviaaJNIcompiler
//generatedstub)exceptduringtestingandimagewriting.
CHECK(!Runtime::Current()->IsStarted());
Objectreceiver=method->IsStatic()?NULL:shadow_frame->GetVRegReference(0);
uint32_targs=shadow_frame->GetVRegArgs(method->IsStatic()?0:1);
UnstartedRuntimeJni(self,method,receiver,args,result);
}
self->PopShadowFrame();
return;
}
这个函数定义在文件art/runtime/interpreter/interpreter.cc中。
对比函数artInterpreterToInterpreterBridge和artQuickToInterpreterBridge的实现就可以看出,虽然都是要跳入到解释器去执行一个被调用类方法,但是两者的实现是不一样的。前者由于调用方法本来就是在解释器中执行的,因此,调用被调用类方法所需要的解释器栈帧实际上已经准备就绪,并且被调用方法的DEX字节码也已经知晓,因此这时候就可以直接调用另外一个函数Execute来继续在解释器中执行。
同样,如果被调用的类方法是一个静态方法,并且它的声明类还没有被初始化,那么就需要调用ClassLinker类的成员函数EnsureInitialized来确保它的声明类是已经初始化好了的。
如果被调用的类方法是一个JNI方法,那么此种情况在ART运行时已经启动之后不允许的(ART运行时启动之前允许,但是只是测试ART运行时时才会用到),因为JNI方法在解释器中有自己的调用方式,而函数函数artInterpreterToInterpreterBridge仅仅是用于调用非JNI方法,因此这时候就会调用另外一个函数UnstartedRuntimeJni记录和抛出错误。
函数GetResolutionTrampoline用来获得一个延迟链接类方法的函数。这个延迟链接类方法的函数用作那些在类加载时还没有链接好的方法的调用入口点,也就是还没有确定调用入口的类方法。对于已经链接好的类方法来说,无论它是解释执行,还是本地机器指令执行,相应的调用入口都是已经通过ArtMehtod类的成员函数SetEntryPointFromCompiledCode和SetEntryPointFromInterpreter设置好了的。如上所述,这类典型的类方法就是静态方法,它们需要等到类初始化的时候才会进行链接。
函数GetResolutionTrampoline的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
staticinlineconstvoidGetResolutionTrampoline(ClassLinkerclass_linker){
#ifdefined(ART_USE_PORTABLE_COMPILER)
returnGetPortableResolutionTrampoline(class_linker);
#else
returnGetQuickResolutionTrampoline(class_linker);
#endif
}
这个函数定义在文件art/runtime/entrypoints/entrypoint_utils.h中。
我们假设没有定义宏ART_USE_PORTABLE_COMPILER,那么接下来就会调用GetQuickResolutionTrampoline来获得一个函数指针。
函数GetQuickResolutionTrampoline的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
staticinlineconstvoidGetQuickResolutionTrampoline(ClassLinkerclass_linker){
returnclass_linker->GetQuickResolutionTrampoline();
}
这个函数定义在文件art/runtime/entrypoints/entrypoint_utils.h中。
函数GetQuickResolutionTrampoline又是通过调用参数class_linker指向的ClassLinker对象的成员函数GetQuickResolutionTrampoline来获得一个函数指针的。
ClassLinker类的成员函数GetQuickResolutionTrampoline的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
classClassLinker{
public:
......
constvoidGetQuickResolutionTrampoline()const{
returnquick_resolution_trampoline_;
}
......
private:
......
constvoidquick_resolution_trampoline_;
......
};
这个函数定义在文件art/runtime/class_linker.h中。
ClassLinker类的成员函数GetQuickResolutionTrampoline返回的是成员变量quick_resolution_trampoline_的值。那么ClassLinker类的成员变量quick_resolution_trampoline_的值是什么时候初始化的呢?是在ClassLinker类的成员函数InitFromImage中初始化。如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidClassLinker::InitFromImage(){
......
gc::Heapheap=Runtime::Current()->GetHeap();
gc::space::ImageSpacespace=heap->GetImageSpace();
......
OatFile&oat_file=GetImageOatFile(space);
......
quick_resolution_trampoline_=oat_file.GetOatHeader().GetQuickResolutionTrampoline();
......
}
这个函数定义在文件art/runtime/class_linker.h中。
从前面这篇文章可以知道,ART运行时在启动的时候,会加载一个Image文件,并且根据这个Image文件创建一个Image空间。这个Image空间属于ART运行时堆的一部分。后面我们分析ART运行时的垃圾收集机制再详细分析。
Image文件是ART运行时第一次启动时翻译系统启动类的DEX字节码创建的OAT文件的过程中创建的。我们将这个OAT文件称为启动OAT文件。这个启动OAT文件的OAT头部包含有一个quick_resolution_trampoline_offset_字段。这个quick_resolution_trampoline_offset_字段指向一小段Trampoline代码。这一小段Trampoline代码的作用是找到当前线程类型为Quick的函数跳转表中的pQuickResolutionTrampoline项,并且跳到这个pQuickResolutionTrampoline项指向的函数去执行。
从上面的分析可以知道,类型为Quick的函数跳转表中的pQuickResolutionTrampoline项指向的函数为art_quick_resolution_trampoline,它是一个用汇编语言实现的函数,如下所示:
[plain]viewplaincopy在CODE上查看代码片派生到我的代码片
.externartQuickResolutionTrampoline
ENTRYart_quick_resolution_trampoline
SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME
movr2,r9@passThread::Current
movr3,sp@passSP
blxartQuickResolutionTrampoline@(Methodcalled,receiver,Thread,SP)
cbzr0,1f@iscodepointernull?gotoexception
movr12,r0
ldrr0,[sp,#0]@loadresolvedmethodinr0
ldrr1,[sp,#8]@restorenon-calleesaver1
ldrdr2,[sp,#12]@restorenon-calleesavesr2-r3
ldrlr,[sp,#44]@restorelr
addsp,#48@rewindsp
.cfi_adjust_cfa_offset-48
bxr12@tail-callintoactualcode
1:
RESTORE_REF_AND_ARGS_CALLEE_SAVE_FRAME
DELIVER_PENDING_EXCEPTION
ENDart_quick_resolution_trampoline
这个函数定义在文件art/runtime/arch/arm/quick_entrypoints_arm.S中。
函数art_quick_resolution_trampoline首先是调用另外一个函数artQuickResolutionTrampoline来获得真正要调用的函数的地址,并且通过bx指令跳到该地址去执行。函数artQuickResolutionTrampoline的作用就是用来延迟链接类方法的,也就是等到该类方法被调用时才会对它进行解析链接,确定真正要调用的函数。
函数artQuickResolutionTrampoline的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
//Lazilyresolveamethodforquick.Calledbystubcode.
extern"C"constvoidartQuickResolutionTrampoline(mirror::ArtMethodcalled,
mirror::Objectreceiver,
Threadthread,mirror::ArtMethodsp)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_){
FinishCalleeSaveFrameSetup(thread,sp,Runtime::kRefsAndArgs);
......
//StartnewJNIlocalreferencestate
JNIEnvExtenv=thread->GetJniEnv();
ScopedObjectAccessUncheckedsoa(env);
......
//Computedetailsaboutthecalledmethod(avoidGCs)
ClassLinkerlinker=Runtime::Current()->GetClassLinker();
mirror::ArtMethodcaller=QuickArgumentVisitor::GetCallingMethod(sp);
InvokeTypeinvoke_type;
constDexFiledex_file;
uint32_tdex_method_idx;
if(called->IsRuntimeMethod()){
uint32_tdex_pc=caller->ToDexPc(QuickArgumentVisitor::GetCallingPc(sp));
constDexFile::CodeItemcode;
{
MethodHelpermh(caller);
dex_file=&mh.GetDexFile();
code=mh.GetCodeItem();
}
constInstructioninstr=Instruction::At(&code->insns_[dex_pc]);
Instruction::Codeinstr_code=instr->Opcode();
boolis_range;
switch(instr_code){
caseInstruction::INVOKE_DIRECT:
invoke_type=kDirect;
is_range=false;
break;
caseInstruction::INVOKE_DIRECT_RANGE:
invoke_type=kDirect;
is_range=true;
break;
caseInstruction::INVOKE_STATIC:
invoke_type=kStatic;
is_range=false;
break;
caseInstruction::INVOKE_STATIC_RANGE:
invoke_type=kStatic;
is_range=true;
break;
caseInstruction::INVOKE_SUPER:
invoke_type=kSuper;
is_range=false;
break;
caseInstruction::INVOKE_SUPER_RANGE:
invoke_type=kSuper;
is_range=true;
break;
caseInstruction::INVOKE_VIRTUAL:
invoke_type=kVirtual;
is_range=false;
break;
caseInstruction::INVOKE_VIRTUAL_RANGE:
invoke_type=kVirtual;
is_range=true;
break;
caseInstruction::INVOKE_INTERFACE:
invoke_type=kInterface;
is_range=false;
break;
caseInstruction::INVOKE_INTERFACE_RANGE:
invoke_type=kInterface;
is_range=true;
break;
default:
LOG(FATAL)<<"Unexpectedcallintotrampoline:"<DumpString(NULL);
//Avoiduseduninitializedwarnings.
invoke_type=kDirect;
is_range=false;
}
dex_method_idx=(is_range)?instr->VRegB_3rc():instr->VRegB_35c();
}else{
invoke_type=kStatic;
dex_file=&MethodHelper(called).GetDexFile();
dex_method_idx=called->GetDexMethodIndex();
}
......
//Resolvemethodfillingindexcache.
if(called->IsRuntimeMethod()){
called=linker->ResolveMethod(dex_method_idx,caller,invoke_type);
}
constvoidcode=NULL;
if(LIKELY(!thread->IsExceptionPending())){
......
//Refinecalledmethodbasedonreceiver.
if(invoke_type==kVirtual){
called=receiver->GetClass()->FindVirtualMethodForVirtual(called);
}elseif(invoke_type==kInterface){
called=receiver->GetClass()->FindVirtualMethodForInterface(called);
}
//Ensurethatthecalledmethod''sclassisinitialized.
mirror::Classcalled_class=called->GetDeclaringClass();
linker->EnsureInitialized(called_class,true,true);
if(LIKELY(called_class->IsInitialized())){
code=called->GetEntryPointFromCompiledCode();
}elseif(called_class->IsInitializing()){
if(invoke_type==kStatic){
//Classisstillinitializing,gotooatandgrabcode(trampolinemustbeleftinplace
//untilclassisinitializedtostopracesbetweenthreads).
code=linker->GetOatCodeFor(called);
}else{
//Notrampolinefornon-staticmethods.
code=called->GetEntryPointFromCompiledCode();
}
}
......
}
......
//Placecalledmethodincallee-saveframetobeplacedasfirstargumenttoquickmethod.
sp=called;
returncode;
}
这个函数定义在文件art/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc中。
第一个参数called表示被调用的类方法,第二个参数receiver表示被调用的对象,也就是接收消息的对象,第三个参数thread表示当前线程,第四个参数sp指向调用栈顶。通过调用QuickArgumentVisitor类的静态成员函数GetCallingMethod可以在调用栈找到类方法called的调用者,保存在变量caller中。
被调用类方法called有可能是一个运行时方法(RuntimeMethod)。运行时方法相当是一个替身,它是用来找到被替换的类方法。当调用类方法called是一个运行时方法时,调用它的成员函数IsRuntimeMethod得到的返回值为true,这时候我们就需要找到被替换的类方法。那么问题就来了,怎么找到此时被替换的类方法呢?运行时方法只是一个空壳,没有任何线索可以提供给我们,不过我们却可以在DEX字节码的调用指令中找到一些蜘丝马迹。在DEX字节码中,我们在一个类方法中通过invoke-static/invoke-direct/invoke-interface/invoke-super/invoke-virtual等指令来调用另外一个类方法。在这些调用指令中,有一个寄存器记录了被调用的类方法在DEX文件中的方法索引dex_method_index。有了这个DEX文件方法索引之后,我们就可以在相应的DEX文件找到被替换的类方法了。现在第二个问题又来了,我们要在哪一个DEX文件查找被替换的类方法呢?函数artQuickResolutionTrampoline适用的是调用方法caller和被调用方法called均是位于同一个DEX文件的情况。因此,我们可以通过调用方法caller来得到要查找的DEX文件dex_file。有了上述两个重要的信息之后,函数artQuickResolutionTrampoline接下来就可以调用ClassLinker类的成员函数ResolveMethod来查找被替换的类方法了,并且继续保存在参数called中。另一方面,如果被调用类方法called不是运行时方法,那么情况就简单多了,因为此时called描述的便是要调用的类方法。
经过上面的处理之后,参数called指向的ArtMethod对象还不一定是最终要调用的类方法。这是因为当前发生的可能是一个虚函数调用或者接口调用。在上述两种情况下,我们需要通过接收消息的对象receiver来确定真正被调用的类方法。为了完成这个任务,我们首先通过调用Object类的成员函数GetClass获得接收消息的对象receiver的类对象,接着再通过调用过Class类的成员函数FindVirtualMethodForVirtual或者FindVirtualMethodForInterface来获得真正要被调用的类方法。前者针对的是虚函数调用,而后者针对的是接口调用。
最终我们得到的真正被调用的类方法仍然是保存在参数called中。这时候事情还没完,因为此时被调用的类方法所属的类可能还没有初始化好。因此,在继续下一步操作之前,我们需要调用ClassLinker类的成员函数EnsureInitialized来确保存被调用类方法called所属的类已经初始好了。在调用ClassLinker类的成员函数EnsureInitialized的时候,如果被调用类方法called所属的类还没有初始化,那么就会对它进行初始化,不过不等它初始化完成就返回了。因此,这时候就可能会出现两种情况。
第一种情况是被调用类方法called所属的类已经初始好了。这时候我们就可以直接调用它的成员函数GetEntryPointFromCompiledCode来获得它的本地机器指令或者DEX字节码,取决于它是以本地机器指令方式执行还是通过解释器来执行。
第二种情况是被调用方法called所属的类正在初始化中。这时候需要区分静态和非静态调用两种情况。在进一步解释之前,我们需要明确,类加载和类初始化是两个不同的操作。类加载的过程并不一定会伴随着类的初始化。此时我们唯一确定的是被调用方法called所属的类已经被加载(否则它的类方法无法被调用)。又从前面这篇文章可以知道,当一个类被加载时,除了它的静态成员函数,其余所有的成员函数均已加载完毕。这意味着我们可以直接调用ArtMethod类的成员函数GetEntryPointFromCompiledCode来获得被调用方法called的本地机器指令或者DEX字节码。对于静态成员函数的情况,我们就唯有到DEX文件去查找到被调用方法called的本地机器指令了。这是通过调用ClassLinker类的成员函数GetOatCodeFor来实现的。当然,如果该静态成员函数不存在本地机器指令,那么ClassLinker类的成员函数GetOatCodeFor返回的是进入解释器的入口函数地址。这样我们就可以通过解释器来执行该静态成员函数了。
最后,函数artQuickResolutionTrampoline将获得的真正被调用的类方法的执行入口地址code返回给前一个函数,即art_quick_resolution_trampoline,以便后者可以通过bx跳过去执行。函数artQuickResolutionTrampoline在返回之前,同时还会将此时栈顶的内容设置为真正被调用的类方法对象,以便真正被调用的类方法在运行时,可以获得正确的调用栈帧。
到这里,函数artQuickResolutionTrampoline的实现就分析完成了。不过对于上面提到的运行时方法,我们还需要继续解释。只有了理解了运行时方法的作用之后,我们才能真正理解函数artQuickResolutionTrampoline的作用。
运行时方法与另一个称为DexCache的机制有关。在ART运行时中,每一个DEX文件都有一个关联的DexCache,用来缓存对应的DEX文件中已经被解析过的信息,例如类方法和类属性等。这个DexCache使用类DexCache来描述,它的定义如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
classMANAGEDDexCache:publicObject{
public:
......
private:
......
ObjectArrayresolved_methods_;
.....
uint32_tdex_file_;
......
};
这个类定义在文件rt/runtime/mirror/dex_cache.h中。
这里我们只关注DexCache中的类方法,它通过成员变量resolved_methods_来描述。
在ART运行时中,每当一个类被加载时,ART运行时都会检查该类所属的DEX文件是否已经关联有一个DexCache。如果还没有关联,那么就会创建一个DexCache,并且建立好关联关系。以Java层的DexFile类为例,当我们通过调用它的成员函数loadClass来加载一个类的时候,最终会调用到C++层的JNI函数DexFile_defineClassNative来执行真正的加载操作。
函数DexFile_defineClassNative的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
staticjclassDexFile_defineClassNative(JNIEnvenv,jclass,jstringjavaName,jobjectjavaLoader,
jintcookie){
ScopedObjectAccesssoa(env);
constDexFiledex_file=toDexFile(cookie);
......
ScopedUtfCharsclass_name(env,javaName);
......
conststd::stringdescriptor(DotToDescriptor(class_name.c_str()));
constDexFile::ClassDefdex_class_def=dex_file->FindClassDef(descriptor.c_str());
......
ClassLinkerclass_linker=Runtime::Current()->GetClassLinker();
class_linker->RegisterDexFile(dex_file);
mirror::ClassLoaderclass_loader=soa.Decode(javaLoader);
mirror::Classresult=class_linker->DefineClass(descriptor.c_str(),class_loader,dex_file,
dex_class_def);
......
returnsoa.AddLocalReference(result);
}
这个函数定义在文件art/runtime/native/dalvik_system_DexFile.cc中。
参数cookie指向之前已经打开了的DEX文件,因此这里首先将它转换为一个DexFile指针dex_file。这个DEX文件是包含在OAT文件里面的,它们的打开过程可以参考一文。得到了之前打开的DEX文件之后,接下来就调用ClassLinker类的成员函数RegisterDexFile将它注册到ART运行时中去,以便以后可以查询使用。最后再通过ClassLinker类的成员函数DefineClass来加载参数javaName指定的类。
类的加载过程可以参考前面一文,接下来我们主要关注ClassLinker类的成员函数RegisterDexFile的实现,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidClassLinker::RegisterDexFileLocked(constDexFile&dex_file,SirtRef&dex_cache){
dex_lock_.AssertExclusiveHeld(Thread::Current());
CHECK(dex_cache.get()!=NULL)< CHECK(dex_cache->GetLocation()->Equals(dex_file.GetLocation()))
<GetLocation()->ToModifiedUtf8()<<""< dex_caches_.push_back(dex_cache.get());
dex_cache->SetDexFile(&dex_file);
dex_caches_dirty_=true;
}
voidClassLinker::RegisterDexFile(constDexFile&dex_file){
Threadself=Thread::Current();
{
ReaderMutexLockmu(self,dex_lock_);
if(IsDexFileRegisteredLocked(dex_file)){
return;
}
}
//Don''tallocwhileholdingthelock,sinceallocationmayneedto
//suspendallthreadsandanotherthreadmayneedthedex_lock_to
//gettoasuspendpoint.
SirtRefdex_cache(self,AllocDexCache(self,dex_file));
CHECK(dex_cache.get()!=NULL)<<"Failedtoallocatedexcachefor"< {
WriterMutexLockmu(self,dex_lock_);
if(IsDexFileRegisteredLocked(dex_file)){
return;
}
RegisterDexFileLocked(dex_file,dex_cache);
}
}
这个函数定义在文件art/runtime/class_linker.cc中。
ClassLinker类的成员函数RegisterDexFile首先将调用另外一个成员函数IsDexFileRegisteredLocked检查参数dex_file指定的DEX文件是否已经注册过了。如果已经注册过了,那么就什么也不用做。否则的话,就调用ClassLinker类的成员函数AllocDexCache为其分配一个DexCache,并且调用ClassLinker类的成员函数RegisterDexFileLocked执行真正的注册工作。
从上面的分析就可以看出,注册DEX文件实际上就是创建一个与之关联的DexCache,并且将该DexCache保存在ClassLinker类的成员变量dex_caches_所描述的一个向量中。不过,这里我们关注的是注册过程中所创建的DexCache。因此,接下来我们继续分析ClassLinker类的成员函数AllocDexCache的实现,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
mirror::DexCacheClassLinker::AllocDexCache(Threadself,constDexFile&dex_file){
gc::Heapheap=Runtime::Current()->GetHeap();
mirror::Classdex_cache_class=GetClassRoot(kJavaLangDexCache);
SirtRefdex_cache(self,
down_cast(heap->AllocObject(self,dex_cache_class,
dex_cache_class->GetObjectSize())));
......
SirtRef
location(self,intern_table_->InternStrong(dex_file.GetLocation().c_str()));
......
SirtRef>
strings(self,AllocStringArray(self,dex_file.NumStringIds()));
......
SirtRef>
types(self,AllocClassArray(self,dex_file.NumTypeIds()));
......
SirtRef>
methods(self,AllocArtMethodArray(self,dex_file.NumMethodIds()));
......
SirtRef>
fields(self,AllocArtFieldArray(self,dex_file.NumFieldIds()));
......
SirtRef>
initialized_static_storage(self,
AllocObjectArray(self,dex_file.NumTypeIds()));
......
dex_cache->Init(&dex_file,
location.get(),
strings.get(),
types.get(),
methods.get(),
fields.get(),
initialized_static_storage.get());
returndex_cache.get();
}
这个函数定义在文件art/runtime/class_linker.cc中。
我们要创建的DexCache使用java.lang.DexCache类来描述。java.lang.DexCache类对象保存在ART运行时的Image空间中,我们可以通过ClassLinker类的成员函数GetClassRoot来获得的。获得了用来描述java.lang.DexCache类的类对象之后,我们就可以调用Heap类的成员函数AllocObject在ART运行堆上分配一个DexCache对象了。关于ART运行时的Image空间和堆,我们接下来的文章中将会详细分析。
DexCache的作用是用来缓存包含在一个DEX文件里面的类型(Type)、方法(Method)、域(Field)、字符串(String)和静态储存区(StaticStorage)等信息。因此,我们需要为上述提到的信息分配储存空间。例如,对于类方法来说,我们需要创建一个ArtMethod对象指针数组,其中每一个ArtMethod对象都用来描述DEX文件里面的一个类方法。有了这些储存空间之后,最后就可以调用DexCache类的成员函数Init对刚才创建的DexCache进行初始化了。
DexCache类的成员函数Init的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidDexCache::Init(constDexFiledex_file,
Stringlocation,
ObjectArraystrings,
ObjectArrayresolved_types,
ObjectArrayresolved_methods,
ObjectArrayresolved_fields,
ObjectArrayinitialized_static_storage){
......
SetFieldObject(ResolvedMethodsOffset(),resolved_methods,false);
......
Runtimeruntime=Runtime::Current();
if(runtime->HasResolutionMethod()){
//Initializetheresolvemethodsarraytocontaintrampolinesforresolution.
ArtMethodtrampoline=runtime->GetResolutionMethod();
size_tlength=resolved_methods->GetLength();
for(size_ti=0;i resolved_methods->SetWithoutChecks(i,trampoline);
}
}
}
这个函数定义在文件art/runtime/mirror/dex_cache.cc中。
我们重点关注DexCache中的类方法对象数组的初始化。前面提到,DexCache类有一个类型为ObjectArray的resolved_methods_指针数组。我们通过DexCache类的成员函数ResolvedMethodsOffset可以知道它在DexCache类中的偏移值。有了这个偏移值之后,就可以调用父类Object的成员函数SetFieldObject来将参数resolved_methods描述的ArtMethod对象指针数组设置到当前正在初始化的DexCache对象的成员变量resolved_methods_去了。
接下来重点就来了,DexCache类的成员变量resolved_methods_指向的ArtMethod对象指针数组中的每一个ArtMethod指针都会指向同一个ResolutionMethod。这个ResolutionMethod可以通过Runtime类的成员函数GetResolutionMethod获得,它的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
classRuntime{
public:
......
//Returnsaspecialmethodthatcallsintoatrampolineforruntimemethodresolution
mirror::ArtMethodGetResolutionMethod()const{
CHECK(HasResolutionMethod());
returnresolution_method_;
}
......
private:
......
mirror::ArtMethodresolution_method_;
......
};
这个函数定义在文件rt/runtime/runtime.h中。
Runtime类的成员函数GetResolutionMethod返回的是成员变量resolution_method_指向的一个ArtMethod对象。
那么Runtime类的成员变量resolution_method_是什么时候初始化的呢?是在ART运行时的Image空间初始化过程中初始化的。在前面一篇文章中,我们提到,ART运行时的Image空间创建完成之后,会调用ImageSpace类的成员函数Init来对它进行初始化。
ImageSpace类的成员函数Init的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
ImageSpaceImageSpace::Init(conststd::string&image_file_name,boolvalidate_oat_file){
......
UniquePtrfile(OS::OpenFileForReading(image_file_name.c_str()));
......
ImageHeaderimage_header;
boolsuccess=file->ReadFully(&image_header,sizeof(image_header));
......
UniquePtrimage_map(MemMap::MapFileAtAddress(nullptr,image_header.GetImageBitmapSize(),
PROT_READ,MAP_PRIVATE,
file->Fd(),image_header.GetBitmapOffset(),
......
Runtimeruntime=Runtime::Current();
mirror::Objectresolution_method=image_header.GetImageRoot(ImageHeader::kResolutionMethod);
runtime->SetResolutionMethod(down_cast(resolution_method));
......
}
这个函数定义在文件art/runtime/gc/space/image_space.cc中。
ImageSpace类的成员函数Init首先是将Image文件的头部读取出来,并且根据得到的Image头部信息将Image文件映射到指定的内存位置。Image头部指定了ART运行时使用的ResolutionMethod在内存中的位置,可以通过ImageHeader类的成员函数GetImageRoot来获得。获得了这个ResolutionMethod之后,就可以调用Runtime类的成员函数SetResolutionMethod将它设置到ART运行时去了。
前面说了那么多,好像还是没有发现为什么要给ART运行时设置一个ResolutionMethod。迷局就要准备解开了。在解开之前,我们首先要知道ART运行时使用的ResolutionMethod是长什么样的,也就是它是如何创建的。
ResolutionMethod本质上就一个ArtMethod对象。当我们调用dex2oat工具将系统启动类翻译成本地机器指令时,会创建这个ResolutionMethod,并且将它保存在Image文件中。这样以后要使用这个ResolutionMethod时,就可以将对应的Image文件加载到内存获得。
ResolutionMethod是通过调用Runtime类的成员函数CreateResolutionMethod来创建的,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
mirror::ArtMethodRuntime::CreateResolutionMethod(){
mirror::Classmethod_class=mirror::ArtMethod::GetJavaLangReflectArtMethod();
Threadself=Thread::Current();
SirtRef
method(self,down_cast(method_class->AllocObject(self)));
method->SetDeclaringCwww.shanxiwang.netlass(method_class);
//TODO:useaspecialmethodforresolutionmethodsaves
method->SetDexMethodIndex(DexFile::kDexNoIndex);
//Whencompiling,thecodepointerwillgetsetlaterwhentheimageisloaded.
Runtimer=Runtime::Current();
ClassLinkercl=r->GetClassLinker();
method->SetEntryPointFromCompiledCode(r->IsCompiler()?NULL:GetResolutionTrampoline(cl));
returnmethod.get();
}
这个函数定义在文件art/runtime/runtime.cc中。
从Runtime类的成员函数CreateResolutionMethod的实现就可以看出,ART运行时的ResolutionMethod有以下两个特点:
1.它的DexMethodIndex为DexFile::kDexNoIndex,这是因为它不代表任何的类方法。
2.由于上述原因,它没有相应的本地机器指令,因此它不能执行。
回想我们前面分析的函数artQuickResolutionTrampoline,它通过ArtMethod类的成员函数IsRuntimeMethod来判断一个ArtMethod对象是否是一个运行时方法。ArtMethod类的成员函数IsRuntimeMethod的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
inlineboolArtMethod::IsRuntimeMethod()const{
returnGetDexMethodIndex()==DexFile::kDexNoIndex;
}
这个函数定义在文件art/runtime/mirror/art_method-inl.h文件中。
如果一个ArtMethod的DexMethodIndex等于DexFile::kDexNoIndex,那么它就被认为是运行时方法。当然,ResoultionMethod只是运行方法的其中一种。其中类型的运行时方法我们后面分析ART运行时的Image空间的文章时再讲解。
如前面所述,函数artQuickResolutionTrampoline一旦发现一个接着要调用的类方法是一个运行时方法时,就会调用ClassLinker类的成员函数ResolveMethod来对其进行解析,也就是找到真正要被调用的类方法。
ClassLinker类的成员函数ResolveMethod的实现如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
inlinemirror::ArtMethodClassLinker::ResolveMethod(uint32_tmethod_idx,
constmirror::ArtMethodreferrer,
InvokeTypetype){
mirror::ArtMethodresolved_method=
referrer->GetDexCacheResolvedMethods()->Get(method_idx);
if(UNLIKELY(resolved_method==NULL||resolved_method->IsRuntimeMethod())){
mirror::Classdeclaring_class=referrer->GetDeclaringClass();
mirror::DexCachedex_cache=declaring_class->GetDexCache();
mirror::ClassLoaderclass_loader=declaring_class->GetClassLoader();
constDexFile&dex_file=dex_cache->GetDexFile();
resolved_method=ResolveMethod(dex_file,method_idx,dex_cache,class_loader,referrer,type);
}
returnresolved_method;
}
这个函数定义在文件art/runtime/class_linker-inl.h中。
参数method_idx描述的是接下来将要被调用类方法在DEX文件的索引。注意,每一个类方法在宿主类中有一个索引,在对应的DEX文件中也有一个索引。这两个索引是不一样的,根据前面一文,前一个索引用来查找一个类方法的本地机器指令。而后面一个索引,自然的就是用来DEX文件中找到对应的类方法描述信息了。这意味着一旦知道一个类方法在DEX文件的索引,那么就可以在对应的DEX文件中对该类方法进行解析了。一旦解析完成,自然就可以知道接下来要被调用的类方法是什么了。
参数referrer指向的ArtMethod对象描述的是调用者(类方法)。每一个类方法都关联有一个ArtMethod对象指针数组,这个ArtMethod对象指针数组实际上就是我们在前面提到的DexCache中的ArtMethod对象指针数组。同时,每一个类对象(Class)也关联有一个DexCache。这个DexCache实际上就是与包含该类的DEX文件相关联的DexCache。为了搞清楚上述关系,我们回顾一下前面一文提到的ClassLinker类的两个成员函数DefineClass和LoadMethod。
在ClassLinker类的成员函数DefineClass中,会给每一个加载的类关联一个DexCache,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
mirror::ClassClassLinker::DefineClass(constchardescriptor,
mirror::ClassLoaderclass_loader,
constDexFile&dex_file,
constDexFile::ClassDef&dex_class_def){
Threadself=Thread::Current();
SirtRefklass(self,NULL);
......
klass->SetDexCache(FindDexCache(dex_file));
LoadClass(dex_file,dex_class_def,klass,class_loader);
......
}
这个函数定义在文件art/runtime/class_linker.cc中。
变量klass描述的就是正在加载的类,在对其进行加载之前,首先会调用ClassLinker类的成员函数FindDexCache找到与参数dex_file描述的DEX文件相关联的DexCache。有了这个DexCache,就可以将它设置到kclass指向的Class对象中去了。注意,参数dex_file描述的DEX文件就是包含正在加载的类的文件。
在ClassLinker类的成员函数LoadMethod中,会给每一个加载的类方法设置一个DEX文件类方法索引,以及关联一个ArtMethod对象指针数组,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
mirror::ArtMethodClassLinker::LoadMethod(Threadself,constDexFile&dex_file,
constClassDataItemIterator&it,
SirtRef&klass){
uint32_tdex_method_idx=it.GetMemberIndex();
......
mirror::ArtMethoddst=AllocArtMethod(self);
......
dst->SetDexMethodIndex(dex_method_idx);
......
dst->SetDexCacheResolvedMethods(klass->GetDexCache()->GetResolvedMethods());
......
}
这个函数定义在文件art/runtime/class_linker.cc中。
变量dst描述的便是正在加载的类方法,我们可以通过参数it获得它在DEX文件中的类方法索引,并且将该索引设置到变量dst指向的ArtMethod对象中去。
参数klass描述是正在加载的类方法所属的类,前面我们已经给这个类关联过一个DexCache了,因此,只要将重新获得该DexCache,并且获得该DexCache里面的ArtMethod对象指针数组,那么就可以将ArtMethod对象指针数组设置到正在加载的类方法去了。
从ClassLinker类的两个成员函数DefineClass和LoadMethod的实现就可以看出,同一个DEX文件的所有类关联的DexCache都是同一个DexCache,并且属于这些类的所有类方法关联的ArtMethod对象指针数组都是该DexCache内部的ArtMethod对象指针数组。这个结论对我们理解ClassLinker类的成员函数ResolveMethod的实现很重要。
在ClassLinker类的成员函数ResolveMethod中,我们知道的是调用者以及被调用者在DEX文件中的类方法索引,因此,我们就可以从与调用者关联的ArtMethod对象指针数组中找到接下来真正要被调用的类方法了。
DexCache内部的ArtMethod对象指针数组的每一个ArtMethod指针一开始都是指向ART运行时的ResolutionMethod。但是每当一个类方法第一次被调用的时候,函数artQuickResolutionTrampoline能够根据捕捉到这种情况,并且根据调用者和调用指令的信息,通过ClassLinker类的成员函数ResolveMethod找到接下来真正要被调用的类方法。查找的过程就是解析类方法的过程,这是一个漫长的过程,因为要解析DEX文件。不过一旦接下来要被调用的类方法解析完成,就会创建另外一个ArtMethod对象来描述解析得到的信息,并且将该ArtMethod对象保存在对应的DexCache内部的ArtMethod对象指针数组的相应位置去。这样下次该类方法再被调用时,就不用再次解析了。
从上面的分析我们还可以进一步得到以下的结论:
1.在生成的本地机器指令中,一个类方法调用另外一个类方法并不是直接进行的,而是通过DexCache来间接进行的。
2.通过DexCache间接调用类方法,可以做到延时解析类方法,也就是等到类方法第一次被调用时才解析,这样可以避免解析那些永远不会被调用的类方法。
3.一个类方法只会被解析一次,解析的结果保存在DexCache中,因此当该类方法再次被调用时,就可以直接从DexCache中获得所需要的信息。
以上就是DexCache在ART运行时所起到的作用了,理解这一点对阅读ART运行时的源代码非常重要。
有了以上的知识点之后,接下来我们就可以真正地分析类方法的调用过程了。在一文中,我们通过AndroidRuntime类的成员函数start来分析类和类方法的加载过程。本文同样是从这个函数开始分析类方法的执行过程,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
voidAndroidRuntime::start(constcharclassName,constcharoptions)
{
......
charslashClassName=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);
......
}
}
......
}
这个函数定义在文件frameworks/base/core/jni/AndroidRuntime.cpp。
找到要调用类方法之后,就可以调用JNI接口CallStaticVoidMethod来执行它了。
根据我们在一文的分析可以知道,JNI接口CallStaticVoidMethod由JNI类的成员函数CallStaticVoidMethod实现,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
classJNI{
public:
......
staticvoidCallStaticVoidMethod(JNIEnvenv,jclass,jmethodIDmid,...){
va_listap;
va_start(ap,mid);
CHECK_NON_NULL_ARGUMENT(CallStaticVoidMethod,mid);
ScopedObjectAccesssoa(env);
InvokeWithVarArgs(soa,NULL,mid,ap);
va_end(ap);
}
......
};
这个函数定义在文件art/runtime/jni_internal.cc中。
JNI类的成员函数CallStaticVoidMethod实际上又是通过全局函数InvokeWithVarArgs来调用参数mid指定的方法的,如下所示:
[cpp]viewplaincopy在CODE上查看代码片派生到我的代码片
staticJValueInvokeWithVarArgs(constScopedObjectAccess&soa,jobjectobj,
jmethodIDmid,va_listargs)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_){
ArtMethodmethod=soa.DecodeMethod(mid);
Objectreceiver=method->IsStatic()?NULL:soa.Decode |
|