前面两篇文章介绍了ART的启动过程,而在启动之后,我们感兴趣的就是ART是怎么运行的。回顾一下虚拟机系列的前面几篇文章,我们可以理一下思路:
一,apk以进程的形式运行,进程的创建是由zygote。
参考文章《深入理解Dalvik虚拟机- Android应用进程启动过程分析》
二,进程运行起来之后,初始化JavaVM
参考文章《深入理解ART虚拟机—虚拟机的启动》
三,JavaVM创建之后,我们就有了JNINativeInterface,里面包含了所有的Java接口,比如FindClass,NewObject,CallObjectMethod等
参考文章《深入理解ART虚拟机—虚拟机的启动》
四,Java的运行时的功能简单来说分为:类的加载和函数Method的执行
参考文章《深入理解Dalvik虚拟机- 解释器的运行机制》
art的JNINativeInterface的定义如下:
const JNINativeInterface gJniNativeInterface = {
nullptr, // reserved0.
nullptr, // reserved1.
nullptr, // reserved2.
nullptr, // reserved3.
JNI::GetVersion,
JNI::DefineClass,
JNI::FindClass,
JNI::FromReflectedMethod,
JNI::FromReflectedField,
JNI::ToReflectedMethod,
JNI::GetSuperclass,
JNI::IsAssignableFrom,
JNI::ToReflectedField,
JNI::Throw,
JNI::ThrowNew,
JNI::ExceptionOccurred,
JNI::ExceptionDescribe,
JNI::ExceptionClear,
JNI::FatalError,
JNI::PushLocalFrame,
JNI::PopLocalFrame,
JNI::NewGlobalRef,
JNI::DeleteGlobalRef,
JNI::DeleteLocalRef,
JNI::IsSameObject,
JNI::NewLocalRef,
JNI::EnsureLocalCapacity,
JNI::AllocObject,
JNI::NewObject,
JNI::NewObjectV,
JNI::NewObjectA,
JNI::GetObjectClass,
JNI::IsInstanceOf,
JNI::GetMethodID,
JNI::CallObjectMethod,
JNI::CallObjectMethodV,
JNI::CallObjectMethodA,
JNI::CallBooleanMethod,
JNI::CallBooleanMethodV,
JNI::CallBooleanMethodA,
JNI::CallByteMethod,
JNI::CallByteMethodV,
JNI::CallByteMethodA,
JNI::CallCharMethod,
JNI::CallCharMethodV,
JNI::CallCharMethodA,
JNI::CallShortMethod,
JNI::CallShortMethodV,
JNI::CallShortMethodA,
JNI::CallIntMethod,
JNI::CallIntMethodV,
JNI::CallIntMethodA,
JNI::CallLongMethod,
JNI::CallLongMethodV,
JNI::CallLongMethodA,
JNI::CallFloatMethod,
JNI::CallFloatMethodV,
JNI::CallFloatMethodA,
JNI::CallDoubleMethod,
JNI::CallDoubleMethodV,
JNI::CallDoubleMethodA,
JNI::CallVoidMethod,
JNI::CallVoidMethodV,
JNI::CallVoidMethodA,
JNI::CallNonvirtualObjectMethod,
JNI::CallNonvirtualObjectMethodV,
JNI::CallNonvirtualObjectMethodA,
JNI::CallNonvirtualBooleanMethod,
JNI::CallNonvirtualBooleanMethodV,
JNI::CallNonvirtualBooleanMethodA,
JNI::CallNonvirtualByteMethod,
JNI::CallNonvirtualByteMethodV,
JNI::CallNonvirtualByteMethodA,
JNI::CallNonvirtualCharMethod,
JNI::CallNonvirtualCharMethodV,
JNI::CallNonvirtualCharMethodA,
JNI::CallNonvirtualShortMethod,
JNI::CallNonvirtualShortMethodV,
JNI::CallNonvirtualShortMethodA,
JNI::CallNonvirtualIntMethod,
JNI::CallNonvirtualIntMethodV,
JNI::CallNonvirtualIntMethodA,
JNI::CallNonvirtualLongMethod,
JNI::CallNonvirtualLongMethodV,
JNI::CallNonvirtualLongMethodA,
JNI::CallNonvirtualFloatMethod,
JNI::CallNonvirtualFloatMethodV,
JNI::CallNonvirtualFloatMethodA,
JNI::CallNonvirtualDoubleMethod,
JNI::CallNonvirtualDoubleMethodV,
JNI::CallNonvirtualDoubleMethodA,
JNI::CallNonvirtualVoidMethod,
JNI::CallNonvirtualVoidMethodV,
JNI::CallNonvirtualVoidMethodA,
JNI::GetFieldID,
JNI::GetObjectField,
JNI::GetBooleanField,
JNI::GetByteField,
JNI::GetCharField,
JNI::GetShortField,
JNI::GetIntField,
JNI::GetLongField,
JNI::GetFloatField,
JNI::GetDoubleField,
JNI::SetObjectField,
JNI::SetBooleanField,
JNI::SetByteField,
JNI::SetCharField,
JNI::SetShortField,
JNI::SetIntField,
JNI::SetLongField,
JNI::SetFloatField,
JNI::SetDoubleField,
JNI::GetStaticMethodID,
JNI::CallStaticObjectMethod,
JNI::CallStaticObjectMethodV,
JNI::CallStaticObjectMethodA,
JNI::CallStaticBooleanMethod,
JNI::CallStaticBooleanMethodV,
JNI::CallStaticBooleanMethodA,
JNI::CallStaticByteMethod,
JNI::CallStaticByteMethodV,
JNI::CallStaticByteMethodA,
JNI::CallStaticCharMethod,
JNI::CallStaticCharMethodV,
JNI::CallStaticCharMethodA,
JNI::CallStaticShortMethod,
JNI::CallStaticShortMethodV,
JNI::CallStaticShortMethodA,
JNI::CallStaticIntMethod,
JNI::CallStaticIntMethodV,
JNI::CallStaticIntMethodA,
JNI::CallStaticLongMethod,
JNI::CallStaticLongMethodV,
JNI::CallStaticLongMethodA,
JNI::CallStaticFloatMethod,
JNI::CallStaticFloatMethodV,
JNI::CallStaticFloatMethodA,
JNI::CallStaticDoubleMethod,
JNI::CallStaticDoubleMethodV,
JNI::CallStaticDoubleMethodA,
JNI::CallStaticVoidMethod,
JNI::CallStaticVoidMethodV,
JNI::CallStaticVoidMethodA,
JNI::GetStaticFieldID,
JNI::GetStaticObjectField,
JNI::GetStaticBooleanField,
JNI::GetStaticByteField,
JNI::GetStaticCharField,
JNI::GetStaticShortField,
JNI::GetStaticIntField,
JNI::GetStaticLongField,
JNI::GetStaticFloatField,
JNI::GetStaticDoubleField,
JNI::SetStaticObjectField,
JNI::SetStaticBooleanField,
JNI::SetStaticByteField,
JNI::SetStaticCharField,
JNI::SetStaticShortField,
JNI::SetStaticIntField,
JNI::SetStaticLongField,
JNI::SetStaticFloatField,
JNI::SetStaticDoubleField,
JNI::NewString,
JNI::GetStringLength,
JNI::GetStringChars,
JNI::ReleaseStringChars,
JNI::NewStringUTF,
JNI::GetStringUTFLength,
JNI::GetStringUTFChars,
JNI::ReleaseStringUTFChars,
JNI::GetArrayLength,
JNI::NewObjectArray,
JNI::GetObjectArrayElement,
JNI::SetObjectArrayElement,
JNI::NewBooleanArray,
JNI::NewByteArray,
JNI::NewCharArray,
JNI::NewShortArray,
JNI::NewIntArray,
JNI::NewLongArray,
JNI::NewFloatArray,
JNI::NewDoubleArray,
JNI::GetBooleanArrayElements,
JNI::GetByteArrayElements,
JNI::GetCharArrayElements,
JNI::GetShortArrayElements,
JNI::GetIntArrayElements,
JNI::GetLongArrayElements,
JNI::GetFloatArrayElements,
JNI::GetDoubleArrayElements,
JNI::ReleaseBooleanArrayElements,
JNI::ReleaseByteArrayElements,
JNI::ReleaseCharArrayElements,
JNI::ReleaseShortArrayElements,
JNI::ReleaseIntArrayElements,
JNI::ReleaseLongArrayElements,
JNI::ReleaseFloatArrayElements,
JNI::ReleaseDoubleArrayElements,
JNI::GetBooleanArrayRegion,
JNI::GetByteArrayRegion,
JNI::GetCharArrayRegion,
JNI::GetShortArrayRegion,
JNI::GetIntArrayRegion,
JNI::GetLongArrayRegion,
JNI::GetFloatArrayRegion,
JNI::GetDoubleArrayRegion,
JNI::SetBooleanArrayRegion,
JNI::SetByteArrayRegion,
JNI::SetCharArrayRegion,
JNI::SetShortArrayRegion,
JNI::SetIntArrayRegion,
JNI::SetLongArrayRegion,
JNI::SetFloatArrayRegion,
JNI::SetDoubleArrayRegion,
JNI::RegisterNatives,
JNI::UnregisterNatives,
JNI::MonitorEnter,
JNI::MonitorExit,
JNI::GetJavaVM,
JNI::GetStringRegion,
JNI::GetStringUTFRegion,
JNI::GetPrimitiveArrayCritical,
JNI::ReleasePrimitiveArrayCritical,
JNI::GetStringCritical,
JNI::ReleaseStringCritical,
JNI::NewWeakGlobalRef,
JNI::DeleteWeakGlobalRef,
JNI::ExceptionCheck,
JNI::NewDirectByteBuffer,
JNI::GetDirectBufferAddress,
JNI::GetDirectBufferCapacity,
JNI::GetObjectRefType,
};
这些函数的定义在jni_internal.cc。
我们要分析art的运行机制,就需要弄清楚类的加载和art函数的执行:
一,类的加载
dalvik的类加载我们已经在《深入理解Dalvik虚拟机- Android应用进程启动过程分析》分析了,Android应用进程启动的时候会创建BaseDexClassLoader,这个BaseDexClassLoader包含了自身apk。再回顾一下过程:
1, app_process作为zygote server通过local socket处理进程创建请求,zygote server是在ZygoteInit.main函数里调用ZygoteInit.runSelectLoop监听。
2, 接收到zygote client的fork请求之后,调用ZygoteConnection.runOnce,调用Zygote.forkAndSpecialize创建新进程
3, 进程创建之后,由ZygoteConnection.handleParentProc来初始化进程,最终会调用ActivityThread.main函数
4, ActivityThread.main -> ActivityThread.attach -> ActivityThread.bindApplication -> Activity.handleBindApplication,handleBindApplication会初始化BaseDexClassLoader。
5, 类的加载经过了ClassLoader.loadClass->BaseDexClassLoader.findClass->DexPathList.findClass->DexFile.loadClassBinaryName->DexFile.defineClassNative->DexFile_defineClassNative(runtime/native/dalvik_system_DexFile.cc)
这个初始化过程,art和dalvik都是一样的。art的DexFile_defineClassNative由ClassLinker的DefineClass来加载类。
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;
}
类的加载除了创建Class只外,还有加载类的字段和方法,这个由ClassLinker::LoadClass来完成。
void ClassLinker::LoadClass(Thread* self, const DexFile& dex_file,
const DexFile::ClassDef& dex_class_def,
Handle<mirror::Class> klass) {
const uint8_t* class_data = dex_file.GetClassData(dex_class_def);
if (class_data == nullptr) {
return; // no fields or methods - for example a marker interface
}
bool has_oat_class = false;
if (Runtime::Current()->IsStarted() && !Runtime::Current()->IsAotCompiler()) {
OatFile::OatClass oat_class = FindOatClass(dex_file, klass->GetDexClassDefIndex(),
&has_oat_class);
if (has_oat_class) {
LoadClassMembers(self, dex_file, class_data, klass, &oat_class);
}
}
if (!has_oat_class) {
LoadClassMembers(self, dex_file, class_data, klass, nullptr);
}
}
二,函数的执行
一旦类的加载完成,那么就可以调用类的成员函数了,之前的解释器运行机制那篇文章介绍过,Java的执行是以Method为执行单元的,所以我们分析art的运行机制,其实就是分析Method的运行机制。
《深入理解Dalvik虚拟机- Android应用进程启动过程分析》可知,ActivityThread是进程在启动的时候传类名,在进程启动之后,由handleParentProc执行main函数,因此第一个被执行的java函数是ActivityThread.main。
Process.ProcessStartResult startResult = Process.start("android.app.ActivityThread",
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, app.info.seinfo, null);
ActivityThread.main是最终由AndroidRuntime::callMain执行
status_t AndroidRuntime::callMain(const String8& className, jclass clazz,
const Vector<String8>& args)
{
JNIEnv* env;
jmethodID methodId;
ALOGD("Calling main entry %s", className.string());
env = getJNIEnv();
if (clazz == NULL || env == NULL) {
return UNKNOWN_ERROR;
}
methodId = env->GetStaticMethodID(clazz, "main", "([Ljava/lang/String;)V");
if (methodId == NULL) {
ALOGE("ERROR: could not find method %s.main(String[])\n", className.string());
return UNKNOWN_ERROR;
}
/*
* We want to call main() with a String array with our arguments in it.
* Create an array and populate it.
*/
jclass stringClass;
jobjectArray strArray;
const size_t numArgs = args.size();
stringClass = env->FindClass("java/lang/String");
strArray = env->NewObjectArray(numArgs, stringClass, NULL);
for (size_t i = 0; i < numArgs; i++) {
jstring argStr = env->NewStringUTF(args[i].string());
env->SetObjectArrayElement(strArray, i, argStr);
}
env->CallStaticVoidMethod(clazz, methodId, strArray);
return NO_ERROR;
}
实际会调用JNINativeInterface的CallStaticVoidMethod,上面已经介绍过,该函数的定义在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);
}
InvokeWithVarArgs是执行函数的入口,定义在runtime/reflection.cc,最终是调用了ArtMethod::Invoke
JValue InvokeWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, jobject obj, jmethodID mid,
va_list args)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// We want to make sure that the stack is not within a small distance from the
// protected region in case we are calling into a leaf function whose stack
// check has been elided.
if (UNLIKELY(__builtin_frame_address(0) < soa.Self()->GetStackEnd())) {
ThrowStackOverflowError(soa.Self());
return JValue();
}
ArtMethod* method = soa.DecodeMethod(mid);
bool is_string_init = method->GetDeclaringClass()->IsStringClass() && method->IsConstructor();
if (is_string_init) {
// Replace calls to String.<init> with equivalent StringFactory call.
method = soa.DecodeMethod(WellKnownClasses::StringInitToStringFactoryMethodID(mid));
}
mirror::Object* receiver = method->IsStatic() ? nullptr : soa.Decode<mirror::Object*>(obj);
uint32_t shorty_len = 0;
const char* shorty = method->GetShorty(&shorty_len);
JValue result;
ArgArray arg_array(shorty, shorty_len);
arg_array.BuildArgArrayFromVarArgs(soa, receiver, args);
InvokeWithArgArray(soa, method, &arg_array, &result, shorty);
if (is_string_init) {
// For string init, remap original receiver to StringFactory result.
UpdateReference(soa.Self(), obj, result.GetL());
}
return result;
}
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);
}
我们知道ART的运行模式是AOT的,在apk安装的时候,每个DexMethod都会由dex2oat编译成目标代码,而不再是虚拟机执行的字节码,但同时Dex字节码仍然还在OAT里存在,所以ART的代码执行既支持QuickCompiledCode模式,也同时支持解释器模式以及JIT执行模式。看ArtMethod::Invoke
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
const char* shorty) {
if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEnd())) {
ThrowStackOverflowError(self);
return;
}
if (kIsDebugBuild) {
self->AssertThreadSuspensionIsAllowable();
CHECK_EQ(kRunnable, self->GetState());
CHECK_STREQ(GetInterfaceMethodIfProxy(sizeof(void*))->GetShorty(), 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);
}
Invoke可以进入OAT,Interpreter模式执行Method,如果当前是Interpreter模式,就调用art::interpreter::EnterInterpreterFromInvoke,如果是OAT模式,就调用art_quick_invoke_stub/art_quick_invoke_static_stub。
EnterInterpreterFromInvoke函数里会判断是native还是解释器执行:
void EnterInterpreterFromInvoke(Thread* self, ArtMethod* method, Object* receiver,
uint32_t* args, JValue* result) {
DCHECK_EQ(self, Thread::Current());
bool implicit_check = !Runtime::Current()->ExplicitStackOverflowChecks();
if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEndForInterpreter(implicit_check))) {
ThrowStackOverflowError(self);
return;
}
const char* old_cause = self->StartAssertNoThreadSuspension("EnterInterpreterFromInvoke");
const DexFile::CodeItem* code_item = method->GetCodeItem();
uint16_t num_regs;
uint16_t num_ins;
if (code_item != nullptr) {
num_regs = code_item->registers_size_;
num_ins = code_item->ins_size_;
} else if (method->IsAbstract()) {
self->EndAssertNoThreadSuspension(old_cause);
ThrowAbstractMethodError(method);
return;
} else {
DCHECK(method->IsNative());
num_regs = num_ins = ArtMethod::NumArgRegisters(method->GetShorty());
if (!method->IsStatic()) {
num_regs++;
num_ins++;
}
}
// Set up shadow frame with matching number of reference slots to vregs.
ShadowFrame* last_shadow_frame = self->GetManagedStack()->GetTopShadowFrame();
void* memory = alloca(ShadowFrame::ComputeSize(num_regs));
ShadowFrame* shadow_frame(ShadowFrame::Create(num_regs, last_shadow_frame, method, 0, memory));
self->PushShadowFrame(shadow_frame);
size_t cur_reg = num_regs - num_ins;
if (!method->IsStatic()) {
CHECK(receiver != nullptr);
shadow_frame->SetVRegReference(cur_reg, receiver);
++cur_reg;
}
uint32_t shorty_len = 0;
const char* shorty = method->GetShorty(&shorty_len);
for (size_t shorty_pos = 0, arg_pos = 0; cur_reg < num_regs; ++shorty_pos, ++arg_pos, cur_reg++) {
DCHECK_LT(shorty_pos + 1, shorty_len);
switch (shorty[shorty_pos + 1]) {
case 'L': {
Object* o = reinterpret_cast<StackReference<Object>*>(&args[arg_pos])->AsMirrorPtr();
shadow_frame->SetVRegReference(cur_reg, o);
break;
}
case 'J': case 'D': {
uint64_t wide_value = (static_cast<uint64_t>(args[arg_pos + 1]) << 32) | args[arg_pos];
shadow_frame->SetVRegLong(cur_reg, wide_value);
cur_reg++;
arg_pos++;
break;
}
default:
shadow_frame->SetVReg(cur_reg, args[arg_pos]);
break;
}
}
self->EndAssertNoThreadSuspension(old_cause);
// Do this after populating the shadow frame in case EnsureInitialized causes a GC.
if (method->IsStatic() && UNLIKELY(!method->GetDeclaringClass()->IsInitialized())) {
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
StackHandleScope<1> hs(self);
Handle<mirror::Class> h_class(hs.NewHandle(method->GetDeclaringClass()));
if (UNLIKELY(!class_linker->EnsureInitialized(self, h_class, true, true))) {
CHECK(self->IsExceptionPending());
self->PopShadowFrame();
return;
}
}
if (LIKELY(!method->IsNative())) {
JValue r = Execute(self, code_item, *shadow_frame, JValue());
if (result != nullptr) {
*result = r;
}
} 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.
// Update args to be the args in the shadow frame since the input ones could hold stale
// references pointers due to moving GC.
args = shadow_frame->GetVRegArgs(method->IsStatic() ? 0 : 1);
if (!Runtime::Current()->IsStarted()) {
UnstartedRuntime::Jni(self, method, receiver, args, result);
} else {
InterpreterJni(self, method, shorty, receiver, args, result);
}
}
self->PopShadowFrame();
}
这个函数前面部分都在做参数压栈操作,最后几行进入主题,如果不是Native,那么调用Execute执行;Native函数则调用InterpreterJni。Execute就是art的解释器代码,Dex的字节码是通过ArtMethod::GetCodeItem函数获得,由Execute逐条执行。InterpreterJni通过GetEntryPointFromJni来获得native的函数,并执行。
if (LIKELY(!method->IsNative())) {
JValue r = Execute(self, code_item, *shadow_frame, JValue());
if (result != nullptr) {
*result = r;
}
} 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.
// Update args to be the args in the shadow frame since the input ones could hold stale
// references pointers due to moving GC.
args = shadow_frame->GetVRegArgs(method->IsStatic() ? 0 : 1);
if (!Runtime::Current()->IsStarted()) {
UnstartedRuntime::Jni(self, method, receiver, args, result);
} else {
InterpreterJni(self, method, shorty, receiver, args, result);
}
}
再回调OAT的模式,art_quick_invoke_stub/art_quick_invoke_static_stub最终会调用到art_quick_invoke_stub_internal(arch/arm/quick_entrypoints_arm.S)
ENTRY art_quick_invoke_stub_internal
push {r4, r5, r6, r7, r8, r9, r10, r11, lr} @ spill regs
.cfi_adjust_cfa_offset 16
.cfi_rel_offset r4, 0
.cfi_rel_offset r5, 4
.cfi_rel_offset r6, 8
.cfi_rel_offset r7, 12
.cfi_rel_offset r8, 16
.cfi_rel_offset r9, 20
.cfi_rel_offset r10, 24
.cfi_rel_offset r11, 28
.cfi_rel_offset lr, 32
mov r11, sp @ save the stack pointer
.cfi_def_cfa_register r11
mov r9, r3 @ move managed thread pointer into r9
add r4, r2, #4 @ create space for method pointer in frame
sub r4, sp, r4 @ reserve & align *stack* to 16 bytes: native calling
and r4, #0xFFFFFFF0 @ convention only aligns to 8B, so we have to ensure ART
mov sp, r4 @ 16B alignment ourselves.
mov r4, r0 @ save method*
add r0, sp, #4 @ pass stack pointer + method ptr as dest for memcpy
bl memcpy @ memcpy (dest, src, bytes)
mov ip, #0 @ set ip to 0
str ip, [sp] @ store null for method* at bottom of frame
ldr ip, [r11, #48] @ load fp register argument array pointer
vldm ip, {s0-s15} @ copy s0 - s15
ldr ip, [r11, #44] @ load core register argument array pointer
mov r0, r4 @ restore method*
add ip, ip, #4 @ skip r0
ldm ip, {r1-r3} @ copy r1 - r3
#ifdef ARM_R4_SUSPEND_FLAG
mov r4, #SUSPEND_CHECK_INTERVAL @ reset r4 to suspend check interval
#endif
ldr ip, [r0, #ART_METHOD_QUICK_CODE_OFFSET_32] @ get pointer to the code
blx ip @ call the method
mov sp, r11 @ restore the stack pointer
.cfi_def_cfa_register sp
ldr r4, [sp, #40] @ load result_is_float
ldr r9, [sp, #36] @ load the result pointer
cmp r4, #0
ite eq
strdeq r0, [r9] @ store r0/r1 into result pointer
vstrne d0, [r9] @ store s0-s1/d0 into result pointer
pop {r4, r5, r6, r7, r8, r9, r10, r11, pc} @ restore spill regs
END art_quick_invoke_stub_internal
找到ArtMethod的entry_point_from_quick_compiled_code_字段,这个就是EntryPointFromQuickCompiledCode,从而进入OAT函数执行。
#define ART_METHOD_QUICK_CODE_OFFSET_32 36
ADD_TEST_EQ(ART_METHOD_QUICK_CODE_OFFSET_32,
art::ArtMethod::EntryPointFromQuickCompiledCodeOffset(4).Int32Value())
EntryPointFromQuickCompiledCode的初始化在class_linker的LoadClassMembers时调用的LinkCode,有下面几种类型
1,SetEntryPointFromQuickCompiledCode(GetQuickCode()); // 这个是执行OatMethod
2,SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge()); // Dex Method
3,SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub()); // Native Method
4,SetEntryPointFromQuickCompiledCode(GetQuickResolutionStub()); // method->IsStatic() && !method->IsConstructor()
如果是强制使用了解释器模式,那么执行的是代码GetQuickToInterpreterBridge(non-static, non-native)或GetQuickGenericJniStub(non-static, native)或GetQuickResolutionStub(static),这几个EntryPoint对应的实际执行函数如下。
GetQuickGenericJniStub — artQuickGenericJniTrampoline
GetQuickResolutionStub — artQuickResolutionTrampoline
GetQuickToInterpreterBridge — artQuickToInterpreterBridge
ArtMthod被Resolve之后,如果是走Oat模式就会执行GetQuickCode。
楼上是EntryPointFromQuickCompiledCode的情况:
不同的执行模式有不同的EntryPoint:
1,解释器 - EntryPointFromInterpreter
在interpreter/interpreter_common.cc里会在执行解释器函数时,会获得ArtMethod的Interpret EntryPoint执行
2,Jni - EntryPointFromJni
interpreter/interpreter.cc,InterpreterJni函数会获得ArtMethod的Jni EntryPoint执行
3,Oat - EntryPointFromQuickCompiledCode
DexCache在Init的时候会将Method都初始化为ResolutionMethod,这个Resolution Method是没有dex method id的,是个RuntimeMethod,这是lazy load method,运行时resolve之后才会替换成实际的ArtMethod。
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);
}
}
void DexCache::Fixup(ArtMethod* trampoline, size_t pointer_size) {
// Fixup the resolve methods array to contain trampoline for resolution.
CHECK(trampoline != nullptr);
CHECK(trampoline->IsRuntimeMethod());
auto* resolved_methods = GetResolvedMethods();
for (size_t i = 0, length = resolved_methods->GetLength(); i < length; i++) {
if (resolved_methods->GetElementPtrSize<ArtMethod*>(i, pointer_size) == nullptr) {
resolved_methods->SetElementPtrSize(i, trampoline, pointer_size);
}
}
}
resolution method的EntryPointFromQuickCompiledCode指向GetQuickResolutionStub,意思就是一开始,这些函数的执行点都是从artQuickResolutionTrampoline开始。
// Lazily resolve a method for quick. Called by stub code.
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;
}
上面代码可知,找到当前ArtMethod的流程大致的逻辑就是,根据caller函数ArtMethod的dex代码,可以找到这个ArtMethod的函数调用类型(INVOKE_DIRECT,INVOKE_STATIC,INVOKE_SUPER,INVOKE_VIRTUAL etc.),不同的类型查找的方式不一样,比如Virtual Method要从虚表里找,Super Method要从父类的Method里去找,找到之后调用ClassLinker的ResolveMethod来解析,解析出来的ArtMethod的就是上面LinkCode过的ArtMethod。
下面就是ResolveMethod函数的实现,Calss查找到Method,之后在赋值到DexCache里,这样下次再执行就能直接找到Resolved Method。
ArtMethod* ClassLinker::ResolveMethod(const DexFile& dex_file, uint32_t method_idx,
Handle<mirror::DexCache> dex_cache,
Handle<mirror::ClassLoader> class_loader,
ArtMethod* referrer, InvokeType type) {
DCHECK(dex_cache.Get() != nullptr);
// Check for hit in the dex cache.
ArtMethod* resolved = dex_cache->GetResolvedMethod(method_idx, image_pointer_size_);
if (resolved != nullptr && !resolved->IsRuntimeMethod()) {
DCHECK(resolved->GetDeclaringClassUnchecked() != nullptr) << resolved->GetDexMethodIndex();
return resolved;
}
// Fail, get the declaring class.
const DexFile::MethodId& method_id = dex_file.GetMethodId(method_idx);
mirror::Class* klass = ResolveType(dex_file, method_id.class_idx_, dex_cache, class_loader);
if (klass == nullptr) {
DCHECK(Thread::Current()->IsExceptionPending());
return nullptr;
}
// Scan using method_idx, this saves string compares but will only hit for matching dex
// caches/files.
switch (type) {
case kDirect: // Fall-through.
case kStatic:
resolved = klass->FindDirectMethod(dex_cache.Get(), method_idx, image_pointer_size_);
DCHECK(resolved == nullptr || resolved->GetDeclaringClassUnchecked() != nullptr);
break;
case kInterface:
resolved = klass->FindInterfaceMethod(dex_cache.Get(), method_idx, image_pointer_size_);
DCHECK(resolved == nullptr || resolved->GetDeclaringClass()->IsInterface());
break;
case kSuper: // Fall-through.
case kVirtual:
resolved = klass->FindVirtualMethod(dex_cache.Get(), method_idx, image_pointer_size_);
break;
default:
LOG(FATAL) << "Unreachable - invocation type: " << type;
UNREACHABLE();
}
if (resolved == nullptr) {
// Search by name, which works across dex files.
const char* name = dex_file.StringDataByIdx(method_id.name_idx_);
const Signature signature = dex_file.GetMethodSignature(method_id);
switch (type) {
case kDirect: // Fall-through.
case kStatic:
resolved = klass->FindDirectMethod(name, signature, image_pointer_size_);
DCHECK(resolved == nullptr || resolved->GetDeclaringClassUnchecked() != nullptr);
break;
case kInterface:
resolved = klass->FindInterfaceMethod(name, signature, image_pointer_size_);
DCHECK(resolved == nullptr || resolved->GetDeclaringClass()->IsInterface());
break;
case kSuper: // Fall-through.
case kVirtual:
resolved = klass->FindVirtualMethod(name, signature, image_pointer_size_);
break;
}
}
// If we found a method, check for incompatible class changes.
if (LIKELY(resolved != nullptr && !resolved->CheckIncompatibleClassChange(type))) {
// Be a good citizen and update the dex cache to speed subsequent calls.
dex_cache->SetResolvedMethod(method_idx, resolved, image_pointer_size_);
return resolved;
} else {
// If we had a method, it's an incompatible-class-change error.
if (resolved != nullptr) {
ThrowIncompatibleClassChangeError(type, resolved->GetInvokeType(), resolved, referrer);
} else {
// We failed to find the method which means either an access error, an incompatible class
// change, or no such method. First try to find the method among direct and virtual methods.
const char* name = dex_file.StringDataByIdx(method_id.name_idx_);
const Signature signature = dex_file.GetMethodSignature(method_id);
switch (type) {
case kDirect:
case kStatic:
resolved = klass->FindVirtualMethod(name, signature, image_pointer_size_);
// Note: kDirect and kStatic are also mutually exclusive, but in that case we would
// have had a resolved method before, which triggers the "true" branch above.
break;
case kInterface:
case kVirtual:
case kSuper:
resolved = klass->FindDirectMethod(name, signature, image_pointer_size_);
break;
}
// If we found something, check that it can be accessed by the referrer.
bool exception_generated = false;
if (resolved != nullptr && referrer != nullptr) {
mirror::Class* methods_class = resolved->GetDeclaringClass();
mirror::Class* referring_class = referrer->GetDeclaringClass();
if (!referring_class->CanAccess(methods_class)) {
ThrowIllegalAccessErrorClassForMethodDispatch(referring_class, methods_class, resolved,
type);
exception_generated = true;
} else if (!referring_class->CanAccessMember(methods_class, resolved->GetAccessFlags())) {
ThrowIllegalAccessErrorMethod(referring_class, resolved);
exception_generated = true;
}
}
if (!exception_generated) {
// Otherwise, throw an IncompatibleClassChangeError if we found something, and check
// interface methods and throw if we find the method there. If we find nothing, throw a
// NoSuchMethodError.
switch (type) {
case kDirect:
case kStatic:
if (resolved != nullptr) {
ThrowIncompatibleClassChangeError(type, kVirtual, resolved, referrer);
} else {
resolved = klass->FindInterfaceMethod(name, signature, image_pointer_size_);
if (resolved != nullptr) {
ThrowIncompatibleClassChangeError(type, kInterface, resolved, referrer);
} else {
ThrowNoSuchMethodError(type, klass, name, signature);
}
}
break;
case kInterface:
if (resolved != nullptr) {
ThrowIncompatibleClassChangeError(type, kDirect, resolved, referrer);
} else {
resolved = klass->FindVirtualMethod(name, signature, image_pointer_size_);
if (resolved != nullptr) {
ThrowIncompatibleClassChangeError(type, kVirtual, resolved, referrer);
} else {
ThrowNoSuchMethodError(type, klass, name, signature);
}
}
break;
case kSuper:
if (resolved != nullptr) {
ThrowIncompatibleClassChangeError(type, kDirect, resolved, referrer);
} else {
ThrowNoSuchMethodError(type, klass, name, signature);
}
break;
case kVirtual:
if (resolved != nullptr) {
ThrowIncompatibleClassChangeError(type, kDirect, resolved, referrer);
} else {
resolved = klass->FindInterfaceMethod(name, signature, image_pointer_size_);
if (resolved != nullptr) {
ThrowIncompatibleClassChangeError(type, kInterface, resolved, referrer);
} else {
ThrowNoSuchMethodError(type, klass, name, signature);
}
}
break;
}
}
}
Thread::Current()->AssertPendingException();
return nullptr;
}
}
至此,Art Method的执行机制就算介绍完了,我们对整个函数执行机制都有个全局的概念了,包括:
1,Art怎么进入第一个Method
2,ClassLinker在初始化的时候怎么加载成员函数(初始化几个EntryPoint)
3,DexCache初始化的时候将ArtMethod初始化成Resolution Method,后续在运行时ResolveMethod
4,解释器模式在Art下是如何运行的
作者:threepigs 发表于2016/11/4 16:09:57
原文链接