在类的加载过程中,需要对类的各个方法进行链接,实际上就是确定它们是通过解释器来执行,还是以本地机器指令来直接执行(art/runtime/class_linker.cc),如下所示:
void ClassLinker::LinkCode(ArtMethod* method, const OatFile::OatClass* oat_class,
uint32_t class_def_method_index) {
Runtime* const runtime = Runtime::Current();
if (runtime->IsAotCompiler()) {
// The following code only applies to a non-compiler runtime.
return;
}
// Method shouldn't have already been linked.
DCHECK(method->GetEntryPointFromQuickCompiledCode() == nullptr);
if (oat_class != nullptr) {
// 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(class_def_method_index);
oat_method.LinkMethod(method);
}
// Install entry point from interpreter.
bool enter_interpreter = NeedsInterpreter(method, method->GetEntryPointFromQuickCompiledCode());
if (enter_interpreter && !method->IsNative()) {
method->SetEntryPointFromInterpreter(artInterpreterToInterpreterBridge);
} else {
method->SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge);
}
if (method->IsAbstract()) {
method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge());
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->SetEntryPointFromQuickCompiledCode(GetQuickResolutionStub());
} else if (enter_interpreter) {
if (!method->IsNative()) {
// Set entry point from compiled code if there's no code or in interpreter only mode.
method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge());
} else {
method->SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub());
}
}
if (method->IsNative()) {
// Unregistering restores the dlsym lookup stub.
method->UnregisterNative();
if (enter_interpreter) {
// We have a native method here without code. Then it should have either the generic JNI
// trampoline as entrypoint (non-static), or the resolution trampoline (static).
// TODO: this doesn't handle all the cases where trampolines may be installed.
const void* entry_point = method->GetEntryPointFromQuickCompiledCode();
DCHECK(IsQuickGenericJniStub(entry_point) || IsQuickResolutionStub(entry_point));
}
}
}
函数LinkCode的详细解释可以参考前面Android运行时ART加载类和方法的过程分析一文,这里我们只对结论进行总结,以及对结论进行进一步的分析:
1. ART运行时有两种执行方法:解释执行模式和本地机器指令执行模式。默认是本地机器指令执行模式,但是在启动ART运行时时可以通过-Xint选项指定为解释执行模式。
2. 即使是在本地机器指令模式中,也有类方法可能需要以解释模式执行。反之亦然。解释执行的类方法通过函数artInterpreterToCompiledCodeBridge的返回值调用本地机器指令执行的类方法;本地机器指令执行的类方法通过函数GetQuickToInterpreterBridge的返回值调用解释执行的类方法;解释执行的类方法通过函数artInterpreterToInterpreterBridge的返回值解释执行的类方法。
3. 在解释执行模式下,除了JNI方法和动态Proxy方法,其余所有的方法均通过解释器执行,它们的入口点设置为函数GetQuickToInterpreterBridge的返回值。
4. 抽象方法不能执行,它必须要由子类实现,因此会将抽象方法的入口点设置为函数GetQuickToInterpreterBridge的返回值,目的检测是否在本地机器指令中调用了抽象方法。如果调用了,上述入口点就会抛出一个异常。
5. 静态类方法的执行模式延迟至类初始化确定。在类初始化之前,它们的入口点由函数GetQuickResolutionStub的返回值代理。
接下来,我们就着重分析artInterpreterToCompiledCodeBridge、GetQuickToInterpreterBridge、artInterpreterToInterpreterBridge和GetQuickResolutionStub这4个函数以及它们所返回的函数的实现,以便可以更好地理解上述5个结论。
函数artInterpreterToCompiledCodeBridge用来在解释器中调用以本地机器指令执行的函数,它的实现如下所示(art/runtime/entrypoints/interpreter/interpreter_entrypoints.cc):
extern "C" void artInterpreterToCompiledCodeBridge(Thread* self, const DexFile::CodeItem* code_item,
ShadowFrame* shadow_frame, JValue* result) {
ArtMethod* method = shadow_frame->GetMethod();
// Ensure static methods are initialized.
if (method->IsStatic()) {
mirror::Class* declaringClass = method->GetDeclaringClass();
if (UNLIKELY(!declaringClass->IsInitialized())) {
self->PushShadowFrame(shadow_frame);
StackHandleScope<1> hs(self);
Handle<mirror::Class> h_class(hs.NewHandle(declaringClass));
if (UNLIKELY(!Runtime::Current()->GetClassLinker()->EnsureInitialized(self, h_class, true,
true))) {
self->PopShadowFrame();
DCHECK(self->IsExceptionPending());
return;
}
self->PopShadowFrame();
CHECK(h_class->IsInitializing());
// Reload from shadow frame in case the method moved, this is faster than adding a handle.
method = shadow_frame->GetMethod();
}
}
uint16_t arg_offset = (code_item == nullptr) ? 0 : code_item->registers_size_ - code_item->ins_size_;
method->Invoke(self, shadow_frame->GetVRegArgs(arg_offset),
(shadow_frame->NumberOfVRegs() - arg_offset) * sizeof(uint32_t),
result, method->GetInterfaceMethodIfProxy(sizeof(void*))->GetShorty());
}
被调用的类方法通过一个ArtMethod对象来描述,并且可以在调用栈帧shadow_frame中获得。获得了用来描述被调用方法的ArtMehtod对象之后,就可以调用它的成员函数Invoke来对它进行执行。后面我们就会看到,ArtMethod类的成员函数Invoke会找到类方法的本地机器指令来执行。
在调用类方法的本地机器指令的时候,从解释器调用栈获取的传入参数根据ART运行时使用的是Quick后端还是Portable后端来生成本地机器指令有所不同。不过最终都会ArtMethod类的成员函数Invoke来执行被调用类方法的本地机器指令。
函数GetQuickToInterpreterBridge用来返回一个函数指针,这个函数指针指向的函数用来从以本地机器指令执行的类方法中调用以解释执行的类方法,它的实现如下所示(art/runtime/entrypoints/runtime_asm_entrypoints.h):
static inline const void* GetQuickToInterpreterBridge() {
return reinterpret_cast<const void*>(art_quick_to_interpreter_bridge);
}
以ARM体系结构为例,函数art_quick_to_interpreter_bridge的实现如下所示(art/runtime/arch/arm/quick_entrypoints_arm.S):
ENTRY art_quick_to_interpreter_bridge
SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME r1, r2
mov r1, r9 @ pass Thread::Current
mov r2, sp @ pass SP
blx artQuickToInterpreterBridge @ (Method* method, Thread*, SP)
ldr r2, [r9, #THREAD_EXCEPTION_OFFSET] @ load Thread::Current()->exception_
// Tear down the callee-save frame. Skip arg registers.
add sp, #(FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE - FRAME_SIZE_REFS_ONLY_CALLEE_SAVE)
.cfi_adjust_cfa_offset -(FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE - FRAME_SIZE_REFS_ONLY_CALLEE_SAVE)
RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME
cbnz r2, 1f @ success if no exception is pending
vmov d0, r0, r1 @ store into fpr, for when it's a fpr return...
bx lr @ return on success
1:
DELIVER_PENDING_EXCEPTION
END art_quick_to_interpreter_bridge
很明显,函数art_quick_to_interpreter_bridge通过调用另外一个函数artQuickToInterpreterBridge从本地机器指令进入到解释器中去。
函数artQuickToInterpreterBridge的实现如下所示(art/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc):
extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, ArtMethod** sp)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// Ensure we don't get thread suspension until the object arguments are safely in the shadow
// frame.
ScopedQuickEntrypointChecks sqec(self);
if (method->IsAbstract()) {
ThrowAbstractMethodError(method);
return 0;
} else {
DCHECK(!method->IsNative()) << PrettyMethod(method);
const char* old_cause = self->StartAssertNoThreadSuspension(
"Building interpreter shadow frame");
const DexFile::CodeItem* code_item = method->GetCodeItem();
DCHECK(code_item != nullptr) << PrettyMethod(method);
uint16_t num_regs = code_item->registers_size_;
void* memory = alloca(ShadowFrame::ComputeSize(num_regs));
// No last shadow coming from quick.
ShadowFrame* shadow_frame(ShadowFrame::Create(num_regs, nullptr, method, 0, memory));
size_t first_arg_reg = code_item->registers_size_ - code_item->ins_size_;
uint32_t shorty_len = 0;
auto* non_proxy_method = method->GetInterfaceMethodIfProxy(sizeof(void*));
const char* shorty = non_proxy_method->GetShorty(&shorty_len);
BuildQuickShadowFrameVisitor shadow_frame_builder(sp, method->IsStatic(), shorty, shorty_len,
shadow_frame, first_arg_reg);
shadow_frame_builder.VisitArguments();
const bool needs_initialization =
method->IsStatic() && !method->GetDeclaringClass()->IsInitialized();
// Push a transition back into managed code onto the linked list in thread.
ManagedStack fragment;
self->PushManagedStackFragment(&fragment);
self->PushShadowFrame(shadow_frame);
self->EndAssertNoThreadSuspension(old_cause);
if (needs_initialization) {
// Ensure static method's class is initialized.
StackHandleScope<1> hs(self);
Handle<mirror::Class> h_class(hs.NewHandle(shadow_frame->GetMethod()->GetDeclaringClass()));
if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(self, h_class, true, true)) {
DCHECK(Thread::Current()->IsExceptionPending()) << PrettyMethod(shadow_frame->GetMethod());
self->PopManagedStackFragment(fragment);
return 0;
}
}
JValue result = interpreter::EnterInterpreterFromEntryPoint(self, code_item, shadow_frame);
// Pop transition.
self->PopManagedStackFragment(fragment);
// Request a stack deoptimization if needed
ArtMethod* caller = QuickArgumentVisitor::GetCallingMethod(sp);
if (UNLIKELY(Dbg::IsForcedInterpreterNeededForUpcall(self, caller))) {
self->SetException(Thread::GetDeoptimizationException());
self->SetDeoptimizationReturnValue(result, shorty[0] == 'L');
}
// No need to restore the args since the method has already been run by the interpreter.
return result.GetJ();
}
}
函数artQuickToInterpreterBridge的作用实际上就是找到被调用类方法method的DEX字节码code_item,然后根据调用传入的参数构造一个解释器调用栈帧shadow_frame,最后就可以通过函数interpreter::EnterInterpreterFromEntryPoint进入到解释器去执行了。
既然已经知道了要执行的类方法的DEX字节码,以及已经构造好了要执行的类方法的调用栈帧,我们就不难理解解释器是如何执行该类方法了,具体可以参考一下Dalvik虚拟机的运行过程分析这篇文章描述的Dalvik虚拟机解释器的实现。
如果要执行的类方法method是一个静态方法,那么我们就需要确保它的声明类是已经初始化过了的。如果还没有初始化过,那么就需要调用ClassLinker类的成员函数EnsureInitialized来对它进行初始化。
extern "C" void artInterpreterToInterpreterBridge(Thread* self, const DexFile::CodeItem* code_item,
ShadowFrame* shadow_frame, JValue* result) {
bool implicit_check = !Runtime::Current()->ExplicitStackOverflowChecks();
if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEndForInterpreter(implicit_check))) {
ThrowStackOverflowError(self);
return;
}
self->PushShadowFrame(shadow_frame);
// Ensure static methods are initialized.
const bool is_static = shadow_frame->GetMethod()->IsStatic();
if (is_static) {
mirror::Class* declaring_class = shadow_frame->GetMethod()->GetDeclaringClass();
if (UNLIKELY(!declaring_class->IsInitialized())) {
StackHandleScope<1> hs(self);
HandleWrapper<Class> h_declaring_class(hs.NewHandleWrapper(&declaring_class));
if (UNLIKELY(!Runtime::Current()->GetClassLinker()->EnsureInitialized(
self, h_declaring_class, true, true))) {
DCHECK(self->IsExceptionPending());
self->PopShadowFrame();
return;
}
CHECK(h_declaring_class->IsInitializing());
}
}
if (LIKELY(!shadow_frame->GetMethod()->IsNative())) {
result->SetJ(Execute(self, code_item, *shadow_frame, JValue()).GetJ());
} else {
// We don't expect to be asked to interpret native code (which is entered via a JNI compiler
// generated stub) except during testing and image writing.
CHECK(!Runtime::Current()->IsStarted());
Object* receiver = is_static ? nullptr : shadow_frame->GetVRegReference(0);
uint32_t* args = shadow_frame->GetVRegArgs(is_static ? 0 : 1);
UnstartedRuntime::Jni(self, shadow_frame->GetMethod(), receiver, args, result);
}
self->PopShadowFrame();
}
对比函数artInterpreterToInterpreterBridge和artQuickToInterpreterBridge的实现就可以看出,虽然都是要跳入到解释器去执行一个被调用类方法,但是两者的实现是不一样的。前者由于调用方法本来就是在解释器中执行的,因此,调用被调用类方法所需要的解释器栈帧实际上已经准备就绪,并且被调用方法的DEX字节码也已经知晓,因此这时候就可以直接调用另外一个函数Execute来继续在解释器中执行。
同样,如果被调用的类方法是一个静态方法,并且它的声明类还没有被初始化,那么就需要调用ClassLinker类的成员函数EnsureInitialized来确保它的声明类是已经初始化好了的。
如果被调用的类方法是一个JNI方法,那么此种情况在ART运行时已经启动之后不允许的(ART运行时启动之前允许,但是只是测试ART运行时时才会用到),因为JNI方法在解释器中有自己的调用方式,而函数函数artInterpreterToInterpreterBridge仅仅是用于调用非JNI方法,因此这时候就会调用另外一个函数UnstartedRuntimeJni记录和抛出错误。
函数GetQuickResolutionStub用来获得一个延迟链接类方法的函数。这个延迟链接类方法的函数用作那些在类加载时还没有链接好的方法的调用入口点,也就是还没有确定调用入口的类方法。对于已经链接好的类方法来说,无论它是解释执行,还是本地机器指令执行,相应的调用入口都是已经通过ArtMehtod类的成员函数SetEntryPointFromCompiledCode和SetEntryPointFromInterpreter设置好了的。如上所述,这类典型的类方法就是静态方法,它们需要等到类初始化的时候才会进行链接。
函数GetQuickResolutionStub的实现如下所示(art/runtime/entrypoints/runtime_asm_entrypoints.h):
static inline const void* GetQuickResolutionStub() {
return reinterpret_cast<const void*>(art_quick_resolution_trampoline);
}
以ARM体系结构为例,函数art_quick_resolution_trampoline的实现如下所示(art/runtime/arch/arm/quick_entrypoints_arm.S):
ENTRY art_quick_resolution_trampoline
SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME r2, r3
mov r2, r9 @ pass Thread::Current
mov r3, sp @ pass SP
blx artQuickResolutionTrampoline @ (Method* called, receiver, Thread*, SP)
cbz r0, 1f @ is code pointer null? goto exception
mov r12, r0
ldr r0, [sp, #0] @ load resolved method in r0
RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME
bx r12 @ tail-call into actual code
1:
RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME
DELIVER_PENDING_EXCEPTION
END art_quick_resolution_trampoline
函数art_quick_resolution_trampoline首先是调用另外一个函数artQuickResolutionTrampoline来获得真正要调用的函数的地址,并且通过bx指令跳到该地址去执行。函数artQuickResolutionTrampoline的作用就是用来延迟链接类方法的,也就是等到该类方法被调用时才会对它进行解析链接,确定真正要调用的函数。
函数artQuickResolutionTrampoline的实现(art/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc)如下所示:
extern "C" const void* artQuickResolutionTrampoline(
ArtMethod* called, mirror::Object* receiver, Thread* self, ArtMethod** sp)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
ScopedQuickEntrypointChecks sqec(self);
// Start new JNI local reference state
JNIEnvExt* env = self->GetJniEnv();
ScopedObjectAccessUnchecked soa(env);
ScopedJniEnvLocalRefState env_state(env);
const char* old_cause = self->StartAssertNoThreadSuspension("Quick method resolution set up");
// Compute details about the called method (avoid GCs)
ClassLinker* linker = Runtime::Current()->GetClassLinker();
ArtMethod* caller = QuickArgumentVisitor::GetCallingMethod(sp);
InvokeType invoke_type;
MethodReference called_method(nullptr, 0);
const bool called_method_known_on_entry = !called->IsRuntimeMethod();
if (!called_method_known_on_entry) {
uint32_t dex_pc = caller->ToDexPc(QuickArgumentVisitor::GetCallingPc(sp));
const DexFile::CodeItem* code;
called_method.dex_file = caller->GetDexFile();
code = caller->GetCodeItem();
CHECK_LT(dex_pc, code->insns_size_in_code_units_);
const Instruction* instr = Instruction::At(&code->insns_[dex_pc]);
Instruction::Code instr_code = instr->Opcode();
bool is_range;
switch (instr_code) {
case Instruction::INVOKE_DIRECT:
invoke_type = kDirect;
is_range = false;
break;
case Instruction::INVOKE_DIRECT_RANGE:
invoke_type = kDirect;
is_range = true;
break;
case Instruction::INVOKE_STATIC:
invoke_type = kStatic;
is_range = false;
break;
case Instruction::INVOKE_STATIC_RANGE:
invoke_type = kStatic;
is_range = true;
break;
case Instruction::INVOKE_SUPER:
invoke_type = kSuper;
is_range = false;
break;
case Instruction::INVOKE_SUPER_RANGE:
invoke_type = kSuper;
is_range = true;
break;
case Instruction::INVOKE_VIRTUAL:
invoke_type = kVirtual;
is_range = false;
break;
case Instruction::INVOKE_VIRTUAL_RANGE:
invoke_type = kVirtual;
is_range = true;
break;
case Instruction::INVOKE_INTERFACE:
invoke_type = kInterface;
is_range = false;
break;
case Instruction::INVOKE_INTERFACE_RANGE:
invoke_type = kInterface;
is_range = true;
break;
default:
LOG(FATAL) << "Unexpected call into trampoline: " << instr->DumpString(nullptr);
UNREACHABLE();
}
called_method.dex_method_index = (is_range) ? instr->VRegB_3rc() : instr->VRegB_35c();
} else {
invoke_type = kStatic;
called_method.dex_file = called->GetDexFile();
called_method.dex_method_index = called->GetDexMethodIndex();
}
uint32_t shorty_len;
const char* shorty =
called_method.dex_file->GetMethodShorty(
called_method.dex_file->GetMethodId(called_method.dex_method_index), &shorty_len);
RememberForGcArgumentVisitor visitor(sp, invoke_type == kStatic, shorty, shorty_len, &soa);
visitor.VisitArguments();
self->EndAssertNoThreadSuspension(old_cause);
const bool virtual_or_interface = invoke_type == kVirtual || invoke_type == kInterface;
// Resolve method filling in dex cache.
if (!called_method_known_on_entry) {
StackHandleScope<1> hs(self);
mirror::Object* dummy = nullptr;
HandleWrapper<mirror::Object> h_receiver(
hs.NewHandleWrapper(virtual_or_interface ? &receiver : &dummy));
DCHECK_EQ(caller->GetDexFile(), called_method.dex_file);
called = linker->ResolveMethod(self, called_method.dex_method_index, caller, invoke_type);
}
const void* code = nullptr;
if (LIKELY(!self->IsExceptionPending())) {
// Incompatible class change should have been handled in resolve method.
CHECK(!called->CheckIncompatibleClassChange(invoke_type))
<< PrettyMethod(called) << " " << invoke_type;
if (virtual_or_interface) {
// Refine called method based on receiver.
CHECK(receiver != nullptr) << invoke_type;
ArtMethod* orig_called = called;
if (invoke_type == kVirtual) {
called = receiver->GetClass()->FindVirtualMethodForVirtual(called, sizeof(void*));
} else {
called = receiver->GetClass()->FindVirtualMethodForInterface(called, sizeof(void*));
}
CHECK(called != nullptr) << PrettyMethod(orig_called) << " "
<< PrettyTypeOf(receiver) << " "
<< invoke_type << " " << orig_called->GetVtableIndex();
// We came here because of sharpening. Ensure the dex cache is up-to-date on the method index
// of the sharpened method avoiding dirtying the dex cache if possible.
// Note, called_method.dex_method_index references the dex method before the
// FindVirtualMethodFor... This is ok for FindDexMethodIndexInOtherDexFile that only cares
// about the name and signature.
uint32_t update_dex_cache_method_index = called->GetDexMethodIndex();
if (!called->HasSameDexCacheResolvedMethods(caller)) {
// Calling from one dex file to another, need to compute the method index appropriate to
// the caller's dex file. Since we get here only if the original called was a runtime
// method, we've got the correct dex_file and a dex_method_idx from above.
DCHECK(!called_method_known_on_entry);
DCHECK_EQ(caller->GetDexFile(), called_method.dex_file);
const DexFile* caller_dex_file = called_method.dex_file;
uint32_t caller_method_name_and_sig_index = called_method.dex_method_index;
update_dex_cache_method_index =
called->FindDexMethodIndexInOtherDexFile(*caller_dex_file,
caller_method_name_and_sig_index);
}
if ((update_dex_cache_method_index != DexFile::kDexNoIndex) &&
(caller->GetDexCacheResolvedMethod(
update_dex_cache_method_index, sizeof(void*)) != called)) {
caller->SetDexCacheResolvedMethod(update_dex_cache_method_index, called, sizeof(void*));
}
} else if (invoke_type == kStatic) {
const auto called_dex_method_idx = called->GetDexMethodIndex();
// For static invokes, we may dispatch to the static method in the superclass but resolve
// using the subclass. To prevent getting slow paths on each invoke, we force set the
// resolved method for the super class dex method index if we are in the same dex file.
// b/19175856
if (called->GetDexFile() == called_method.dex_file &&
called_method.dex_method_index != called_dex_method_idx) {
called->GetDexCache()->SetResolvedMethod(called_dex_method_idx, called, sizeof(void*));
}
}
// Ensure that the called method's class is initialized.
StackHandleScope<1> hs(soa.Self());
Handle<mirror::Class> called_class(hs.NewHandle(called->GetDeclaringClass()));
linker->EnsureInitialized(soa.Self(), called_class, true, true);
if (LIKELY(called_class->IsInitialized())) {
if (UNLIKELY(Dbg::IsForcedInterpreterNeededForResolution(self, called))) {
// If we are single-stepping or the called method is deoptimized (by a
// breakpoint, for example), then we have to execute the called method
// with the interpreter.
code = GetQuickToInterpreterBridge();
} else if (UNLIKELY(Dbg::IsForcedInstrumentationNeededForResolution(self, caller))) {
// If the caller is deoptimized (by a breakpoint, for example), we have to
// continue its execution with interpreter when returning from the called
// method. Because we do not want to execute the called method with the
// interpreter, we wrap its execution into the instrumentation stubs.
// When the called method returns, it will execute the instrumentation
// exit hook that will determine the need of the interpreter with a call
// to Dbg::IsForcedInterpreterNeededForUpcall and deoptimize the stack if
// it is needed.
code = GetQuickInstrumentationEntryPoint();
} else {
code = called->GetEntryPointFromQuickCompiledCode();
}
} else if (called_class->IsInitializing()) {
if (UNLIKELY(Dbg::IsForcedInterpreterNeededForResolution(self, called))) {
// If we are single-stepping or the called method is deoptimized (by a
// breakpoint, for example), then we have to execute the called method
// with the interpreter.
code = GetQuickToInterpreterBridge();
} else if (invoke_type == kStatic) {
// Class is still initializing, go to oat and grab code (trampoline must be left in place
// until class is initialized to stop races between threads).
code = linker->GetQuickOatCodeFor(called);
} else {
// No trampoline for non-static methods.
code = called->GetEntryPointFromQuickCompiledCode();
}
} else {
DCHECK(called_class->IsErroneous());
}
}
CHECK_EQ(code == nullptr, self->IsExceptionPending());
// Fixup any locally saved objects may have moved during a GC.
visitor.FixupReferences();
// Place called method in callee-save frame to be placed as first argument to quick method.
*sp = called;
return code;
}
第一个参数called表示被调用的类方法,第二个参数receiver表示被调用的对象,也就是接收消息的对象,第三个参数thread表示当前线程,第四个参数sp指向调用栈顶。通过调用QuickArgumentVisitor类的静态成员函数GetCallingMethod可以在调用栈找到类方法called的调用者,保存在变量caller中。
被调用类方法called有可能是一个运行时方法(Runtime Method)。运行时方法相当是一个替身,它是用来找到被替换的类方法。当调用类方法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所属的类已经初始好了。这时候我们就可以直接调用它的成员函数GetEntryPointFromQuickCompiledCode来获得它的本地机器指令或者DEX字节码,取决于它是以本地机器指令方式执行还是通过解释器来执行。
第二种情况是被调用方法called所属的类正在初始化中。这时候需要区分静态和非静态调用两种情况。在进一步解释之前,我们需要明确,类加载和类初始化是两个不同的操作。类加载的过程并不一定会伴随着类的初始化。此时我们唯一确定的是被调用方法called所属的类已经被加载(否则它的类方法无法被调用)。又从前面Android运行时ART加载类和方法的过程分析这篇文章可以知道,当一个类被加载时,除了它的静态成员函数,其余所有的成员函数均已加载完毕。这意味着我们可以直接调用ArtMethod类的成员函数GetEntryPointFromQuickCompiledCode来获得被调用方法called的本地机器指令或者DEX字节码。对于静态成员函数的情况,我们就唯有到DEX文件去查找到被调用方法called的本地机器指令了。这是通过调用ClassLinker类的成员函数GetQuickOatCodeFor来实现的。当然,如果该静态成员函数不存在本地机器指令,那么ClassLinker类的成员函数GetQuickOatCodeFor返回的是进入解释器的入口函数地址。这样我们就可以通过解释器来执行该静态成员函数了。
最后,函数artQuickResolutionTrampoline将获得的真正被调用的类方法的执行入口地址code返回给前一个函数,即art_quick_resolution_trampoline,以便后者可以通过bx跳过去执行。函数artQuickResolutionTrampoline在返回之前,同时还会将此时栈顶的内容设置为真正被调用的类方法对象,以便真正被调用的类方法在运行时,可以获得正确的调用栈帧。
到这里,函数artQuickResolutionTrampoline的实现就分析完成了。不过对于上面提到的运行时方法,我们还需要继续解释。只有了理解了运行时方法的作用之后,我们才能真正理解函数artQuickResolutionTrampoline的作用。
class MANAGED DexCache FINAL : public Object {
public:
......
private:
......
HeapReference<PointerArray> resolved_methods_;
.....
uint64_t dex_file_;
.....
};
这里我们只关注Dex Cache中的类方法,它通过成员变量resolved_methods_来描述。
在ART运行时中,每当一个类被加载时,ART运行时都会检查该类所属的DEX文件是否已经关联有一个Dex Cache。如果还没有关联,那么就会创建一个Dex Cache,并且建立好关联关系。以Java层的DexFile类为例,当我们通过调用它的成员函数loadClass来加载一个类的时候,最终会调用到C++层的JNI函数DexFile_defineClassNative来执行真正的加载操作。
函数DexFile_defineClassNative的实现如下所示(art/runtime/native/dalvik_system_DexFile.cc):
static jclass DexFile_defineClassNative(JNIEnv* env, jclass, jstring javaName, jobject javaLoader,
jobject cookie) {
std::unique_ptr<std::vector<const DexFile*>> dex_files = ConvertJavaArrayToNative(env, cookie);
if (dex_files.get() == nullptr) {
VLOG(class_linker) << "Failed to find dex_file";
DCHECK(env->ExceptionCheck());
return nullptr;
}
ScopedUtfChars class_name(env, javaName);
if (class_name.c_str() == nullptr) {
VLOG(class_linker) << "Failed to find class_name";
return nullptr;
}
const std::string descriptor(DotToDescriptor(class_name.c_str()));
const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str()));
for (auto& dex_file : *dex_files) {
const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str(), hash);
if (dex_class_def != nullptr) {
ScopedObjectAccess soa(env);
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
class_linker->RegisterDexFile(*dex_file);
StackHandleScope<1> hs(soa.Self());
Handle<mirror::ClassLoader> class_loader(
hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));
mirror::Class* result = class_linker->DefineClass(soa.Self(), descriptor.c_str(), hash,
class_loader, *dex_file, *dex_class_def);
if (result != nullptr) {
VLOG(class_linker) << "DexFile_defineClassNative returning " << result
<< " for " << class_name.c_str();
return soa.AddLocalReference<jclass>(result);
}
}
}
VLOG(class_linker) << "Failed to find dex_class_def " << class_name.c_str();
return nullptr;
}
参数cookie指向之前已经打开了的DEX文件,因此这里首先将它转换为一个DexFile指针dex_file。这个DEX文件是包含在OAT文件里面的,它们的打开过程可以参考
Android运行时ART加载OAT文件的过程分析
一文。得到了之前打开的DEX文件之后,接下来就调用ClassLinker类的成员函数RegisterDexFile将它注册到ART运行时中去,以便以后可以查询使用。最后再通过ClassLinker类的成员函数DefineClass来加载参数javaName指定的类。
类的加载过程可以参考前面 Android运行时ART加载类和方法的过程分析 一文,接下来我们主要关注ClassLinker类的成员函数RegisterDexFile的实现,如下所示(art/runtime/class_linker.cc):
void ClassLinker::RegisterDexFileLocked(const DexFile& dex_file,
Handle<mirror::DexCache> dex_cache) {
dex_lock_.AssertExclusiveHeld(Thread::Current());
CHECK(dex_cache.Get() != nullptr) << dex_file.GetLocation();
CHECK(dex_cache->GetLocation()->Equals(dex_file.GetLocation()))
<< dex_cache->GetLocation()->ToModifiedUtf8() << " " << dex_file.GetLocation();
dex_caches_.push_back(GcRoot<mirror::DexCache>(dex_cache.Get()));
dex_cache->SetDexFile(&dex_file);
if (log_new_dex_caches_roots_) {
// TODO: This is not safe if we can remove dex caches.
new_dex_cache_roots_.push_back(dex_caches_.size() - 1);
}
}
void ClassLinker::RegisterDexFile(const DexFile& dex_file) {
Thread* self = Thread::Current();
{
ReaderMutexLock mu(self, dex_lock_);
if (IsDexFileRegisteredLocked(dex_file)) {
return;
}
}
// Don't alloc while holding the lock, since allocation may need to
// suspend all threads and another thread may need the dex_lock_ to
// get to a suspend point.
StackHandleScope<1> hs(self);
Handle<mirror::DexCache> dex_cache(hs.NewHandle(AllocDexCache(self, dex_file)));
CHECK(dex_cache.Get() != nullptr) << "Failed to allocate dex cache for "
<< dex_file.GetLocation();
{
WriterMutexLock mu(self, dex_lock_);
if (IsDexFileRegisteredLocked(dex_file)) {
return;
}
RegisterDexFileLocked(dex_file, dex_cache);
}
}
ClassLinker类的成员函数RegisterDexFile首先将调用另外一个成员函数IsDexFileRegisteredLocked检查参数dex_file指定的DEX文件是否已经注册过了。如果已经注册过了,那么就什么也不用做。否则的话,就调用ClassLinker类的成员函数AllocDexCache为其分配一个Dex Cache,并且调用ClassLinker类的成员函数RegisterDexFileLocked执行真正的注册工作。
从上面的分析就可以看出,注册DEX文件实际上就是创建一个与之关联的Dex Cache,并且将该Dex Cache保存在ClassLinker类的成员变量dex_caches_所描述的一个向量中。不过,这里我们关注的是注册过程中所创建的Dex Cache。因此,接下来我们继续分析ClassLinker类的成员函数AllocDexCache的实现,如下所示(art/runtime/class_linker.cc):
mirror::DexCache* ClassLinker::AllocDexCache(Thread* self, const DexFile& dex_file) {
StackHandleScope<6> hs(self);
auto dex_cache(hs.NewHandle(down_cast<mirror::DexCache*>(
GetClassRoot(kJavaLangDexCache)->AllocObject(self))));
if (dex_cache.Get() == nullptr) {
self->AssertPendingOOMException();
return nullptr;
}
auto location(hs.NewHandle(intern_table_->InternStrong(dex_file.GetLocation().c_str())));
if (location.Get() == nullptr) {
self->AssertPendingOOMException();
return nullptr;
}
auto strings(hs.NewHandle(AllocStringArray(self, dex_file.NumStringIds())));
if (strings.Get() == nullptr) {
self->AssertPendingOOMException();
return nullptr;
}
auto types(hs.NewHandle(AllocClassArray(self, dex_file.NumTypeIds())));
if (types.Get() == nullptr) {
self->AssertPendingOOMException();
return nullptr;
}
auto methods(hs.NewHandle(AllocPointerArray(self, dex_file.NumMethodIds())));
if (methods.Get() == nullptr) {
self->AssertPendingOOMException();
return nullptr;
}
auto fields(hs.NewHandle(AllocPointerArray(self, dex_file.NumFieldIds())));
if (fields.Get() == nullptr) {
self->AssertPendingOOMException();
return nullptr;
}
dex_cache->Init(&dex_file, location.Get(), strings.Get(), types.Get(), methods.Get(),
fields.Get(), image_pointer_size_);
return dex_cache.Get();
}
我们要创建的Dex Cache使用java.lang.DexCache类来描述。java.lang.DexCache类对象保存在ART运行时的Image空间中,我们可以通过ClassLinker类的成员函数GetClassRoot来获得的。获得了用来描述java.lang.DexCache类的类对象之后,我们就可以调用Heap类的成员函数AllocObject在ART运行堆上分配一个DexCache对象了。关于ART运行时的Image空间和堆,我们接下来的文章中将会详细分析。
Dex Cache的作用是用来缓存包含在一个DEX文件里面的类型(Type)、方法(Method)、域(Field)、字符串(String)和静态储存区(Static Storage)等信息。因此,我们需要为上述提到的信息分配储存空间。例如,对于类方法来说,我们需要创建一个ArtMethod对象指针数组,其中每一个ArtMethod对象都用来描述DEX文件里面的一个类方法。有了这些储存空间之后,最后就可以调用DexCache类的成员函数Init对刚才创建的Dex Cache进行初始化了。
DexCache类的成员函数Init的实现如下所示(art/runtime/mirror/dex_cache.cc):void DexCache::Init(const DexFile* dex_file, String* location, ObjectArray<String>* strings,
ObjectArray<Class>* resolved_types, PointerArray* resolved_methods,
PointerArray* resolved_fields, size_t pointer_size) {
CHECK(dex_file != nullptr);
CHECK(location != nullptr);
CHECK(strings != nullptr);
CHECK(resolved_types != nullptr);
CHECK(resolved_methods != nullptr);
CHECK(resolved_fields != nullptr);
SetDexFile(dex_file);
SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(DexCache, location_), location);
SetFieldObject<false>(StringsOffset(), strings);
SetFieldObject<false>(ResolvedFieldsOffset(), resolved_fields);
SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(DexCache, resolved_types_), resolved_types);
SetFieldObject<false>(ResolvedMethodsOffset(), resolved_methods);
Runtime* const runtime = Runtime::Current();
if (runtime->HasResolutionMethod()) {
// Initialize the resolve methods array to contain trampolines for resolution.
Fixup(runtime->GetResolutionMethod(), pointer_size);
}
}
我们重点关注Dex Cache中的类方法对象数组的初始化。前面提到,DexCache类有一个类型为ObjectArray<ArtMethod>的resolved_methods_指针数组。我们通过DexCache类的成员函数ResolvedMethodsOffset可以知道它在DexCache类中的偏移值。有了这个偏移值之后,就可以调用父类Object的成员函数SetFieldObject来将参数resolved_methods描述的ArtMethod对象指针数组设置到当前正在初始化的DexCache对象的成员变量resolved_methods_去了。
接下来重点就来了,DexCache类的成员变量resolved_methods_指向的ArtMethod对象指针数组中的每一个ArtMethod指针都会指向同一个Resolution Method。这个Resolution Method可以通过Runtime类的成员函数GetResolutionMethod获得,它的实现如下所示:
// Returns a special method that calls into a trampoline for runtime method resolution
ArtMethod* GetResolutionMethod() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
此处丢失流程,等更新。。。
已找到,继续更新。。。
上述GetResolutionMethod找错地方了,实际是在(art/runtime/runtime-inl.h)中实现:
inline ArtMethod* Runtime::GetResolutionMethod() {
CHECK(HasResolutionMethod());
return resolution_method_;
}
这个方法返回的是(
art/runtime/
runtime.h)这个类的resolution_method_对象,这个对象指向
的一个ArtMethod对象。
那么Runtime类的成员变量resolution_method_是什么时候初始化的呢?是在ART运行时的Image空间初始化过程中初始化的。在前面Android运行时ART加载OAT文件的过程分析一篇文章中,我们提到,ART运行时的Image空间创建完成之后,会调用ImageSpace类的成员函数Init来对它进行初始化。
ImageSpace类的成员函数Init的实现如下所示(art/runtime/gc/space/image_space.cc):
ImageSpace* ImageSpace::Init(const std::string& image_file_name, bool validate_oat_file) {
......
UniquePtr<File> file(OS::OpenFileForReading(image_file_name.c_str()));
......
ImageHeader image_header;
bool success = file->ReadFully(&image_header, sizeof(image_header));
......
UniquePtr<MemMap> image_map(MemMap::MapFileAtAddress(nullptr, image_header.GetImageBitmapSize(),
PROT_READ, MAP_PRIVATE,
file->Fd(), image_header.GetBitmapOffset(),
......
Runtime* runtime = Runtime::Current();
runtime->SetResolutionMethod(image_header.GetImageMethod(ImageHeader::kResolutionMethod);
......
}
ImageSpace类的成员函数Init首先是将Image文件的头部读取出来,并且根据得到的Image头部信息将Image文件映射到指定的内存位置。Image头部指定了ART运行时使用的Resolution Method在内存中的位置,可以通过ImageHeader类的成员函数GetImageRoot来获得。获得了这个Resolution Method之后,就可以调用Runtime类的成员函数SetResolutionMethod将它设置到ART运行时去了。
前面说了那么多,好像还是没有发现为什么要给ART运行时设置一个Resolution Method。迷局就要准备解开了。在解开之前,我们首先要知道ART运行时使用的Resolution Method是长什么样的,也就是它是如何创建的。
Resolution Method本质上就一个ArtMethod对象。当我们调用dex2oat工具将系统启动类翻译成本地机器指令时,会创建这个Resolution Method,并且将它保存在Image文件中。这样以后要使用这个Resolution Method时,就可以将对应的Image文件加载到内存获得。
Resolution Method是通过调用Runtime类的成员函数CreateResolutionMethod来创建的,如下所示(art/runtime/runtime.cc):
ArtMethod* Runtime::CreateResolutionMethod() {
auto* method = Runtime::Current()->GetClassLinker()->CreateRuntimeMethod();
// When compiling, the code pointer will get set later when the image is loaded.
if (IsAotCompiler()) {
size_t pointer_size = GetInstructionSetPointerSize(instruction_set_);
method->SetEntryPointFromQuickCompiledCodePtrSize(nullptr, pointer_size);
} else {
method->SetEntryPointFromQuickCompiledCode(GetQuickResolutionStub());
}
return method;
}
CreateRuntimeMethod方法跳转到(art/runtime/class_linker.cc)执行:
ArtMethod* ClassLinker::CreateRuntimeMethod() {
ArtMethod* method = AllocArtMethodArray(Thread::Current(), 1);
CHECK(method != nullptr);
method->SetDexMethodIndex(DexFile::kDexNoIndex);
CHECK(method->IsRuntimeMethod());
return method;
}
从Runtime类的成员函数CreateResolutionMethod的实现就可以看出,ART运行时的Resolution Method有以下两个特点:
1. 它的Dex Method Index为DexFile::kDexNoIndex,这是因为它不代表任何的类方法。
2. 由于上述原因,它没有相应的本地机器指令,因此它不能执行。
回想我们前面分析的函数artQuickResolutionTrampoline,它通过ArtMethod类的成员函数IsRuntimeMethod来判断一个ArtMethod对象是否是一个运行时方法。ArtMethod类的成员函数IsRuntimeMethod的实现如下所示(art/runtime/mirror/art_method-inl.h):inline bool ArtMethod::IsRuntimeMethod() {
return dex_method_index_ == DexFile::kDexNoIndex;
}
如果一个ArtMethod的Dex Method Index等于DexFile::kDexNoIndex,那么它就被认为是运行时方法。当然,Resoultion Method只是运行方法的其中一种。其中类型的运行时方法我们后面分析ART运行时的Image空间的文章时再讲解。
如前面所述,函数artQuickResolutionTrampoline一旦发现一个接着要调用的类方法是一个运行时方法时,就会调用ClassLinker类的成员函数ResolveMethod来对其进行解析,也就是找到真正要被调用的类方法。
ClassLinker类的成员函数ResolveMethod的实现如下所示(art/runtime/class_linker-inl.h):inline ArtMethod* ClassLinker::ResolveMethod(Thread* self, uint32_t method_idx,
ArtMethod* referrer, InvokeType type) {
ArtMethod* resolved_method = GetResolvedMethod(method_idx, referrer);
if (UNLIKELY(resolved_method == nullptr)) {
mirror::Class* declaring_class = referrer->GetDeclaringClass();
StackHandleScope<2> hs(self);
Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(declaring_class->GetDexCache()));
Handle<mirror::ClassLoader> h_class_loader(hs.NewHandle(declaring_class->GetClassLoader()));
const DexFile* dex_file = h_dex_cache->GetDexFile();
resolved_method = ResolveMethod(*dex_file, method_idx, h_dex_cache, h_class_loader, referrer,
type);
}
// Note: We cannot check here to see whether we added the method to the cache. It
// might be an erroneous class, which results in it being hidden from us.
return resolved_method;
}
参数method_idx描述的是接下来将要被调用类方法在DEX文件的索引。注意,每一个类方法在宿主类中有一个索引,在对应的DEX文件中也有一个索引。这两个索引是不一样的,根据前面Android运行时ART加载类和方法的过程分析一文,前一个索引用来查找一个类方法的本地机器指令。而后面一个索引,自然的就是用来DEX文件中找到对应的类方法描述信息了。这意味着一旦知道一个类方法在DEX文件的索引,那么就可以在对应的DEX文件中对该类方法进行解析了。一旦解析完成,自然就可以知道接下来要被调用的类方法是什么了。
参数referrer指向的ArtMethod对象描述的是调用者(类方法)。每一个类方法都关联有一个ArtMethod对象指针数组,这个ArtMethod对象指针数组实际上就是我们在前面提到的Dex Cache中的ArtMethod对象指针数组。同时,每一个类对象(Class)也关联有一个Dex Cache。这个Dex Cache实际上就是与包含该类的DEX文件相关联的Dex Cache。为了搞清楚上述关系,我们回顾一下前面Android运行时ART加载类和方法的过程分析一文提到的ClassLinker类的两个成员函数DefineClass和LoadMethod(以下回顾内容为老罗原文内容,6.0源码差别不大,请自行查看)。
在ClassLinker类的成员函数DefineClass中,会给每一个加载的类关联一个Dex Cache,如下所示(art/runtime/class_linker.cc):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);
......
klass->SetDexCache(FindDexCache(dex_file));
LoadClass(dex_file, dex_class_def, klass, class_loader);
......
}
变量klass描述的就是正在加载的类,在对其进行加载之前,首先会调用ClassLinker类的成员函数FindDexCache找到与参数dex_file描述的DEX文件相关联的Dex Cache。有了这个Dex Cache,就可以将它设置到kclass指向的Class对象中去了。注意,参数dex_file描述的DEX文件就是包含正在加载的类的文件。
在ClassLinker类的成员函数LoadMethod中,会给每一个加载的类方法设置一个DEX文件类方法索引,以及关联一个ArtMethod对象指针数组,如下所示:
mirror::ArtMethod* ClassLinker::LoadMethod(Thread* self, const DexFile& dex_file,
const ClassDataItemIterator& it,
SirtRef<mirror::Class>& klass) {
uint32_t dex_method_idx = it.GetMemberIndex();
......
mirror::ArtMethod* dst = AllocArtMethod(self);
......
dst->SetDexMethodIndex(dex_method_idx);
......
dst->SetDexCacheResolvedMethods(klass->GetDexCache()->GetResolvedMethods());
......
}
变量dst描述的便是正在加载的类方法,我们可以通过参数it获得它在DEX文件中的类方法索引,并且将该索引设置到变量dst指向的ArtMethod对象中去。
参数klass描述是正在加载的类方法所属的类,前面我们已经给这个类关联过一个Dex Cache了,因此,只要将重新获得该Dex Cache,并且获得该Dex Cache里面的ArtMethod对象指针数组,那么就可以将ArtMethod对象指针数组设置到正在加载的类方法去了。
从ClassLinker类的两个成员函数DefineClass和LoadMethod的实现就可以看出,同一个DEX文件的所有类关联的Dex Cache都是同一个Dex Cache,并且属于这些类的所有类方法关联的ArtMethod对象指针数组都是该Dex Cache内部的ArtMethod对象指针数组。这个结论对我们理解ClassLinker类的成员函数ResolveMethod的实现很重要。
在ClassLinker类的成员函数ResolveMethod中,我们知道的是调用者以及被调用者在DEX文件中的类方法索引,因此,我们就可以从与调用者关联的ArtMethod对象指针数组中找到接下来真正要被调用的类方法了。
Dex Cache内部的ArtMethod对象指针数组的每一个ArtMethod指针一开始都是指向ART运行时的Resolution Method。但是每当一个类方法第一次被调用的时候,函数artQuickResolutionTrampoline能够根据捕捉到这种情况,并且根据调用者和调用指令的信息,通过ClassLinker类的成员函数ResolveMethod找到接下来真正要被调用的类方法。查找的过程就是解析类方法的过程,这是一个漫长的过程,因为要解析DEX文件。不过一旦接下来要被调用的类方法解析完成,就会创建另外一个ArtMethod对象来描述解析得到的信息,并且将该ArtMethod对象保存在对应的Dex Cache内部的ArtMethod对象指针数组的相应位置去。这样下次该类方法再被调用时,就不用再次解析了。
从上面的分析我们还可以进一步得到以下的结论:
1. 在生成的本地机器指令中,一个类方法调用另外一个类方法并不是直接进行的,而是通过Dex Cache来间接进行的。
2. 通过Dex Cache间接调用类方法,可以做到延时解析类方法,也就是等到类方法第一次被调用时才解析,这样可以避免解析那些永远不会被调用的类方法。
3. 一个类方法只会被解析一次,解析的结果保存在Dex Cache中,因此当该类方法再次被调用时,就可以直接从Dex Cache中获得所需要的信息。
以上就是Dex Cache在ART运行时所起到的作用了,理解这一点对阅读ART运行时的源代码非常重要。
void AndroidRuntime::start(const char* className, const char* options)
{
......
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);
......
}
}
......
}
找到要调用类方法之后,就可以调用JNI接口CallStaticVoidMethod来执行它了。
根据我们在Android运行时ART加载类和方法的过程分析一文的分析可以知道,JNI接口CallStaticVoidMethod由JNI类的成员函数CallStaticVoidMethod实现,如下所示(art/runtime/jni_internal.cc):
static void CallStaticVoidMethod(JNIEnv* env, jclass, jmethodID mid, ...) {
va_list ap;
va_start(ap, mid);
CHECK_NON_NULL_ARGUMENT_RETURN_VOID(mid);
ScopedObjectAccess soa(env);
InvokeWithVarArgs(soa, nullptr, mid, ap);
va_end(ap);
}
JNI类的成员函数CallStaticVoidMethod实际上又是通过全局函数InvokeWithVarArgs来调用参数mid指定的方法的,如下所示(art/runtime/reflection.cc):
static void InvokeWithArgArray(const ScopedObjectAccessAlreadyRunnable& soa,
ArtMethod* method, ArgArray* arg_array, JValue* result,
const char* shorty)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
uint32_t* args = arg_array->GetArray();
if (UNLIKELY(soa.Env()->check_jni)) {
CheckMethodArguments(soa.Vm(), method->GetInterfaceMethodIfProxy(sizeof(void*)), args);
}
method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, shorty);
}
函数InvokeWithArgArray通过ArtMethod类的成员函数Invoke来调用参数method指定的类方法。
ArtMethod类的成员函数Invoke的实现如下所示:
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
const char* shorty) {
......
// Push a transition back into managed code onto the linked list in thread.
ManagedStack fragment;
self->PushManagedStackFragment(&fragment);
Runtime* runtime = Runtime::Current();
// Call the invoke stub, passing everything as arguments.
// If the runtime is not yet started or it is required by the debugger, then perform the
// Invocation by the interpreter.
if (UNLIKELY(!runtime->IsStarted() || Dbg::IsForcedInterpreterNeededForCalling(self, this))) {
if (IsStatic()) {
art::interpreter::EnterInterpreterFromInvoke(self, this, nullptr, args, result);
} else {
mirror::Object* receiver =
reinterpret_cast<StackReference<mirror::Object>*>(&args[0])->AsMirrorPtr();
art::interpreter::EnterInterpreterFromInvoke(self, this, receiver, args + 1, result);
}
} else {
DCHECK_EQ(runtime->GetClassLinker()->GetImagePointerSize(), sizeof(void*));
constexpr bool kLogInvocationStartAndReturn = false;
bool have_quick_code = GetEntryPointFromQuickCompiledCode() != nullptr;
if (LIKELY(have_quick_code)) {
if (kLogInvocationStartAndReturn) {
LOG(INFO) << StringPrintf(
"Invoking '%s' quick code=%p static=%d", PrettyMethod(this).c_str(),
GetEntryPointFromQuickCompiledCode(), static_cast<int>(IsStatic() ? 1 : 0));
}
// Ensure that we won't be accidentally calling quick compiled code when -Xint.
if (kIsDebugBuild && runtime->GetInstrumentation()->IsForcedInterpretOnly()) {
DCHECK(!runtime->UseJit());
CHECK(IsEntrypointInterpreter())
<< "Don't call compiled code when -Xint " << PrettyMethod(this);
}
#if defined(__LP64__) || defined(__arm__) || defined(__i386__)
if (!IsStatic()) {
(*art_quick_invoke_stub)(this, args, args_size, self, result, shorty);
} else {
(*art_quick_invoke_static_stub)(this, args, args_size, self, result, shorty);
}
#else
(*art_quick_invoke_stub)(this, args, args_size, self, result, shorty);
#endif
if (UNLIKELY(self->GetException() == Thread::GetDeoptimizationException())) {
// Unusual case where we were running generated code and an
// exception was thrown to force the activations to be removed from the
// stack. Continue execution in the interpreter.
self->ClearException();
ShadowFrame* shadow_frame =
self->PopStackedShadowFrame(StackedShadowFrameType::kDeoptimizationShadowFrame);
result->SetJ(self->PopDeoptimizationReturnValue().GetJ());
self->SetTopOfStack(nullptr);
self->SetTopOfShadowStack(shadow_frame);
interpreter::EnterInterpreterFromDeoptimize(self, shadow_frame, result);
}
if (kLogInvocationStartAndReturn) {
LOG(INFO) << StringPrintf("Returned '%s' quick code=%p", PrettyMethod(this).c_str(),
GetEntryPointFromQuickCompiledCode());
}
} else {
LOG(INFO) << "Not invoking '" << PrettyMethod(this) << "' code=null";
if (result != nullptr) {
result->SetJ(0);
}
}
}
// Pop transition.
self->PopManagedStackFragment(fragment);
}
ArtMethod类的成员函数Invoke的执行逻辑如下所示:
1. 构造一个类型为ManagedStack的调用栈帧。这些调用栈帧会保存在当前线程对象的一个链表中,在进行垃圾收集会使用到。
2. 如果ART运行时还没有启动,那么这时候是不能够调用任何类方法的,因此就直接返回。否则,继续往下执行。
3. 从前面的函数LinkCode可以知道,无论一个类方法是通过解释器执行,还是直接以本地机器指令执行,均可以通过ArtMethod类的成员函数GetEntryPointFromCompiledCode获得其入口点,并且该入口不为NULL。不过,这里并没有直接调用该入口点,而是通过Stub来间接调用。如果方法为非静态,那么使用的Stub就为art_quick_invoke_stub。否则的话,使用的Stub就为art_quick_invoke_static_stub。
4. 如果在执行类方法的过程中,出现了异常,那么就在运行生成的本地机器指令出现了问题,这时候就通过解释器来继续执行。每次通过解释器执行一个类方法的时候,都需要构造一个类型为ShadowFrame的调用栈帧。这些调用栈帧同样是在垃圾回收时使用到。
接下来我们主要是分析第3步,并且假设目标CPU体系架构为ARM,以及ART运行时使用的是Quick类型的后端,这样第3步使用的Stub就为函数art_quick_invoke_stub,它的实现如下所示(art/runtime/arch/arm64/quick_entrypoints_arm64.S):
/*
* extern"C" void art_quick_invoke_stub(ArtMethod *method, x0
* uint32_t *args, x1
* uint32_t argsize, w2
* Thread *self, x3
* JValue *result, x4
* char *shorty); x5
* +----------------------+
* | |
* | C/C++ frame |
* | LR'' |
* | FP'' | <- SP'
* +----------------------+
* +----------------------+
* | x28 | <- TODO: Remove callee-saves.
* | : |
* | x19 |
* | SP' |
* | X5 |
* | X4 | Saved registers
* | LR' |
* | FP' | <- FP
* +----------------------+
* | uint32_t out[n-1] |
* | : : | Outs
* | uint32_t out[0] |
* | ArtMethod* | <- SP value=null
* +----------------------+
*
* Outgoing registers:
* x0 - Method*
* x1-x7 - integer parameters.
* d0-d7 - Floating point parameters.
* xSELF = self
* SP = & of ArtMethod*
* x1 = "this" pointer.
*
*/
ENTRY art_quick_invoke_stub
// Spill registers as per AACPS64 calling convention.
INVOKE_STUB_CREATE_FRAME
// Fill registers x/w1 to x/w7 and s/d0 to s/d7 with parameters.
// Parse the passed shorty to determine which register to load.
// Load addresses for routines that load WXSD registers.
adr x11, .LstoreW2
adr x12, .LstoreX2
adr x13, .LstoreS0
adr x14, .LstoreD0
// Initialize routine offsets to 0 for integers and floats.
// x8 for integers, x15 for floating point.
mov x8, #0
mov x15, #0
add x10, x5, #1 // Load shorty address, plus one to skip return value.
ldr w1, [x9],#4 // Load "this" parameter, and increment arg pointer.
// Loop to fill registers.
.LfillRegisters:
ldrb w17, [x10], #1 // Load next character in signature, and increment.
cbz w17, .LcallFunction // Exit at end of signature. Shorty 0 terminated.
cmp w17, #'F' // is this a float?
bne .LisDouble
cmp x15, # 8*12 // Skip this load if all registers full.
beq .Ladvance4
add x17, x13, x15 // Calculate subroutine to jump to.
br x17
.LisDouble:
cmp w17, #'D' // is this a double?
bne .LisLong
cmp x15, # 8*12 // Skip this load if all registers full.
beq .Ladvance8
add x17, x14, x15 // Calculate subroutine to jump to.
br x17
.LisLong:
cmp w17, #'J' // is this a long?
bne .LisOther
cmp x8, # 6*12 // Skip this load if all registers full.
beq .Ladvance8
add x17, x12, x8 // Calculate subroutine to jump to.
br x17
.LisOther: // Everything else takes one vReg.
cmp x8, # 6*12 // Skip this load if all registers full.
beq .Ladvance4
add x17, x11, x8 // Calculate subroutine to jump to.
br x17
.Ladvance4:
add x9, x9, #4
b .LfillRegisters
.Ladvance8:
add x9, x9, #8
b .LfillRegisters
// Macro for loading a parameter into a register.
// counter - the register with offset into these tables
// size - the size of the register - 4 or 8 bytes.
// register - the name of the register to be loaded.
.macro LOADREG counter size register return
ldr \register , [x9], #\size
add \counter, \counter, 12
b \return
.endm
// Store ints.
.LstoreW2:
LOADREG x8 4 w2 .LfillRegisters
LOADREG x8 4 w3 .LfillRegisters
LOADREG x8 4 w4 .LfillRegisters
LOADREG x8 4 w5 .LfillRegisters
LOADREG x8 4 w6 .LfillRegisters
LOADREG x8 4 w7 .LfillRegisters
// Store longs.
.LstoreX2:
LOADREG x8 8 x2 .LfillRegisters
LOADREG x8 8 x3 .LfillRegisters
LOADREG x8 8 x4 .LfillRegisters
LOADREG x8 8 x5 .LfillRegisters
LOADREG x8 8 x6 .LfillRegisters
LOADREG x8 8 x7 .LfillRegisters
// Store singles.
.LstoreS0:
LOADREG x15 4 s0 .LfillRegisters
LOADREG x15 4 s1 .LfillRegisters
LOADREG x15 4 s2 .LfillRegisters
LOADREG x15 4 s3 .LfillRegisters
LOADREG x15 4 s4 .LfillRegisters
LOADREG x15 4 s5 .LfillRegisters
LOADREG x15 4 s6 .LfillRegisters
LOADREG x15 4 s7 .LfillRegisters
// Store doubles.
.LstoreD0:
LOADREG x15 8 d0 .LfillRegisters
LOADREG x15 8 d1 .LfillRegisters
LOADREG x15 8 d2 .LfillRegisters
LOADREG x15 8 d3 .LfillRegisters
LOADREG x15 8 d4 .LfillRegisters
LOADREG x15 8 d5 .LfillRegisters
LOADREG x15 8 d6 .LfillRegisters
LOADREG x15 8 d7 .LfillRegisters
.LcallFunction:
INVOKE_STUB_CALL_AND_RETURN
END art_quick_invoke_stub
函数art_quick_invoke_ stub前面的注释列出了 函数art_ quick_ invoke_stub被调用的时候,寄存器X0-X5的值,以及调用栈顶端的两个值。其中,X0指向当前被调用的类方法,X1指向一个参数数组地址,W2记录参数数组的大小,X3指向当前线程。调用栈顶端的两个元素分别用来保存调用结果及其类型。
无论一个类方法是通过解释器执行,还是直接以本地机器指令执行,当它被调用时,都有着特殊的调用约定。其中,寄存器xSELF(x18)指向用来描述当前调用线程的一个Thread对象地址,这样本地机器指令在执行的过程中,就可以通过它来定位线程的相关信息,例如我们在前面描述的各种函数跳转表;寄存器r4初始化为一个计数值,当计数值递减至0时,就需要检查当前线程是否已经被挂起;寄存器x0指向用来描述被调用类方法的一个ArtMethod对象地址。
所有传递给被调用方法的参数都会保存在调用栈中,因此,在进入类方法的入口点之前,需要在栈中预留足够的位置,并且通过调用memcpy函数将参数都拷贝到预留的栈位置去。同时,前面7个参数还会额外地保存在寄存器x1-x7中。这样对于小于等于7个参数的类方法,就可以通过访问寄存器来快速地获得参数。
注意,传递给被调用类方法的参数并不是从栈顶第一个位置(一个位置等于一个字长,即8个字节)开始保存的,而是从第二个位置开始的,即sp + 8。这是因为栈顶的第一个位置是预留用来保存用来描述当调用类方法(Caller)的ArtMethod对象地址的。由于函数art_quick_invoke_stub是用来从外部进入到ART运行时的,即不存在调用类方法,因此这时候栈顶第一个位置会被设置为NULL。
准备好调用栈帧之后,就找到从用来描述当前调用类方法的ArtMethod对象地址偏移ART_METHOD_QUICK_CODE_OFFSET_64处的值,并且以该值作为类方法的执行入口点,最后通过blr指令跳过去执行。
中间流程丢失,下面接着老罗的讲。。。
准备好调用栈帧之后,就找到从用来描述当前调用类方法的ArtMethod对象地址偏移METHOD_CODE_OFFSET处的值,并且以该值作为类方法的执行入口点,最后通过blx指令跳过去执行。
METHOD_CODE_OFFSET的值定义在文件art/runtime/asm_support.h中,值为40,如下所示:
参照注释,可以知道,在ArtMethod对象中,偏移值为40的成员变量为entry_point_from_compiled_code_,而该成员变量就是调用函数LinkCode链接类方法时调用ArtMethod类的成员函数SetEntryPointFromCompiledCode设置的。相应的,可以通过ArtMethod类的成员函数GetEntryPointFromCompiledCode获得该成员变量的值,如前面ArtMethod类的成员函数Invoke所示。
在ARM体系架构中,当调用blx指令跳转到指定的地址执行,那么位于blx下面的一条指令会保存在寄存器lr中,而被调用类方法在执行前,一般又会通过SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME宏保存指定的寄存器。
宏SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME的定义如下所示:
这个宏定义在文件art/runtime/arch/arm/quick_entrypoints_arm.S中。
寄存器r1-r3、r5-r8、r10-r11和lr在栈上依次从低地址往高地址保存,即从栈顶开始保存。除了保存上述寄存器之外,还会在栈上预留两个位置(每个位置等于一个字长,即4个字节),其中,栈顶第一个位置用来保存用来描述当前被调用的类方法的ArtMethod对象地址,第二个位置可能是设计用来保存寄存器r0的,但是目前还没有发现这样使用的地方。宏SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME的使用情景之一可能参考我们前面分析的函数art_quick_resolution_trampoline。
现在还有一个问题是,上面提到的栈顶第一位置是什么时候会被设置为用来描述当前被调用的类方法的ArtMethod对象地址的呢?因为宏SETUP_REF_AND_ARGS_CALLEE_SAVE_FRAME只是在栈上预留这个位置,但是没有设置这个位置的值。以我们前面分析的函数artQuickResolutionTrampoline为例,一开始这个位置被函数FinishCalleeSaveFrameSetup设置为一个类型为Runtime::kRefsAndArgs的运行时方法,这是因为这时候我们还不知道真正被调用的类方法是什么。类型为Runtime::kRefsAndArgs的运行时方法与我们前面提到的Resolution Method的作用有点类似,虽然它们本身是一个ArtMethod对象,但是它们的真正作用是用来描述其它的ArtMethod。例如,类型为Runtime::kRefsAndArgs的运行时方法要描述的就是真正要被调用类的类方法会在栈上保存r1-r3、r5-r8、r10-r11和lr这10个寄器,并且会在栈上预留两个额外的位置,其中的一个位置用来保存真正被调用的类方法。果然,等到函数artQuickResolutionTrampoline找到真正被调用的类方法之后,就会将相应的ArtMethod对象地址保存在栈顶上。这正是到函数artQuickResolutionTrampoline返回前所做的事情。
综合上面的分析,我们就得到ART运行时类方法的调用约定,如下所示:
左边显示了一个类方法(Caller)调用另外一个类方法(Callee)时栈的内存布局情况,右边显示了负责安排这些内存布局每一部分的一个情景。
回到前面的函数art_quick_invoke_stub中,通过blx指令调用指定的类方法结束后,结果就保存在r0和r1两个寄存器中,其中一个表示返回值,另外一个表示返回值类型,最后通过strd指令将这两个寄存器的值拷贝到栈上指定的内存地址去,实际上就将调用结果返回给调用者指定的两个变量去。
这样,我们就分析完成类方法的执行过程了,也基本上解释上前面分析的函数LinkCode所涉及到关键函数,包括artInterpreterToCompiledCodeBridge、GetCompiledCodeToInterpreterBridge/GetQuickToInterpreterBridge/art_quick_to_interpreter_bridge/artQuickToInterpreterBridge、artInterpreterToInterpreterBridge、GetResolutionTrampoline/GetQuickResolutionTrampoline/GetQuickResolutionTrampoline/art_quick_resolution_trampoline/artQuickResolutionTrampoline、ArtMethod::SetEntryPointFromCompiledCode/ArtMethod::GetEntryPointFromCompiledCode和ArtMethod::SetEntryPointFromInterpreter等。
为了完整性,接下来我们继续分析一下与函数ArtMethod::SetEntryPointFromInterpreter相对应的函数ArtMethod::GetEntryPointFromInterpreter,以便可以看到由前者设置的函数入口点是在什么情况下被使用的。
前面提到,ArtMethod类的成员函数SetEntryPointFromInterpreter设置的入口点是用来给解释器调用另外一个类方法时使用的。ART运行时的解释器主要由art/runtime/interpreter/interpreter.cc文件中,当它需要调用另外一个类方法时,就会通过函数DoInvoke来实现,如下所示:
这个函数定义在art/runtime/interpreter/interpreter.cc文件中。
通过调用指令中的指定的Dex Method Index,我们可以通过另外一个函数FindMethodFromCode找到被调用的类方法,通过ArtMethod对象method来描述。有了这个ArtMethod对象后,我们就可以调用它的成员函数GetEntryPointFromInterpreter来获得接下来要被调用类方法的执行入口点。从函数LinkCode的实现可以知道,通过ArtMethod类的成员函数GetEntryPointFromInterpreter获得的类方法执行入口点有可能是用来进入解释器的,也有可能是用来进入到类方法的本地机器指令去的。
至此,我们就分析完成ART运行时执行一个类方法的过程以及在执行过程中涉及到的各种关键函数了。本文与其说是分析类方法的执行过程,不如说是分析ART运行时的实现原理。理解了本文分析到的各个关键函数之后,相信对ART运行时就会有一个清晰的认识了。在接下来的文章中,我们将继续发力,分析听起来很高大上的ART运行时垃圾收集机制。敬请关注!想了解更多信息,也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。