在前一篇文章中,我们通过分析OAT文件的加载过程,认识了OAT文件的格式,其中包含了原始的DEX文件。既然ART运行时执行的都是翻译DEX字节码后得到的本地机器指令了,为什么还需要在OAT文件中包含DEX文件,并且将它加载到内存去呢?这是因为ART运行时提供了Java虚拟机接口,而要实现Java虚拟机接口不得不依赖于DEX文件。本文就通过分析ART运行时加载类及其方法的过程来理解DEX文件的作用。
在前面Android运行时ART加载OAT文件的过程分析这篇文章的最后,我们简单总结了ART运行时查找类方法的本地机器指令的过程,如图1所示:
图1 ART运行时查找类方法的本地机器指令的过程
为了方便描述,我们将DEX文件中描述的类和方法称为DEX类(Dex Class)和DEX方法(Dex Method),而将在OAT文件中描述的类和方法称为OAT类(Oat Class)和OAT方法(Oat Method)。接下来我们还会看到,ART运行时在内部又会使用另外两个不同的术语来描述类和方法,其中将类描述为Class,而将类方法描述为ArtMethod。
在图1中,为了找到一个类方法的本地机器指令,我们需要执行以下的操作:
1. 在DEX文件中找到目标DEX类的编号,并且以这个编号为索引,在OAT文件中找到对应的OAT类。
2. 在DEX文件中找到目标DEX方法的编号,并且以这个编号为索引,在上一步找到的OAT类中找到对应的OAT方法。
3. 使用上一步找到的OAT方法的成员变量begin_和codeoffset,计算出该方法对应的本地机器指令。
通过前面Android运行时ART简要介绍和学习计划一文的学习,我们可以知道,ART运行时的入口是com.android.internal.os.ZygoteInit类的静态成员函数main,如下所示:
void AndroidRuntime::start(const char* className, const char* options)
{
......
/* start the virtual machine */
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
if (startVm(&mJavaVM, &env) != 0) {
return;
}
......
/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/
char* slashClassName = toSlashClassName(className);
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
/* keep going */
} else {
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray);
......
}
}
......
}
这个函数定义在文件frameworks/base/core/jni/AndroidRuntime.cpp中。
在AndroidRuntime类的成员函数start中,首先是通过调用函数startVm创建了一个Java虚拟机mJavaVM及其JNI接口env。这个Java虚拟机实际上就是ART运行时。在接下来的描述中,我们将不区分ART虚拟机和ART运行时,并且认为它们表达的是同一个概念。获得了ART虚拟机的JNI接口之后,就可以通过它提供的函数FindClass和GetStaticMethodID来加载com.android.internal.os.ZygoteInit类及其静态成员函数main。于是,最后就可以再通过JNI接口提供的函数CallStaticVoidMethod来调用com.android.internal.os.ZygoteInit类的静态成员函数main,以及进行到ART虚拟机里面去运行。
接下来,我们就通过分析JNI接口FindClass和GetStaticMethodID的实现,以便理解ART运行时是如何查找到指定的类和方法的。在接下来的一篇文章中,我们再分析ART运行时是如何通过JNI接口CallStaticVoidMethod来执行指定类方法的本地机器指令的。
在分析JNI接口FindClass和GetStaticMethodID的实现之前,我们先要讲清楚JNI接口是如何创建的。从前面Android运行时ART加载OAT文件的过程分析一文可以知道,与ART虚拟机主线程关联的JNI接口是在函数JNI_CreateJavaVM中创建的,如下所示:
extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
......
*p_env = Thread::Current()->GetJniEnv();
......
return JNI_OK;
}
这个函数定义在文件art/runtime/jni_internal.cc中。
调用Thread类的静态成员函数Current获得的是用来描述当前线程(即ART虚拟机的主线程)的一个Thread对象,再通过调用这个Thread对象的成员函数GetJniEnv就获得一个JNI接口,并且保存在输出参数p_env中。
Thread类的成员函数GetJniEnv的实现如下所示:
class PACKED(4) Thread {
public:
......
// JNI methods
JNIEnvExt* GetJniEnv() const {
return jni_env_;
}
......
private:
......
// Every thread may have an associated JNI environment
JNIEnvExt* jni_env_;
......
};
这个函数定义在文件art/runtime/thread.h中。
Thread类的成员函数GetJniEnv返回的是成员变量jni_env_指向的一个JNIEnvExt对象。
JNIEnvExt类是从JNIEnv类继承下来的,如下所示:
struct JNIEnvExt : public JNIEnv {
......
};
这个类定义在文件art/runtime/jni_internal.h。
JNIEnv类定义了JNI接口,如下所示:
typedef _JNIEnv JNIEnv;
......
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
......
jint GetVersion()
{ return functions->GetVersion(this); }
......
};
这个类定义在文件libnativehelper/include/nativehelper/jni.h中。
在JNIEnv类中,最重要的就是成员变量functions了,它指向的是一个类型为JNINativeInterface的JNI函数表。所有的JNI接口调用都是通过这个JNI函数表来实现的。例如,用来获得版本号的JNI接口GetVersion就是通过调用JNI函数表中的GetVersion函数来实现的。
那么,上述的JNI函数表是如何创建的呢?通过JNIEnvExt类的构造函数可以知道答案,如下所示:
JNIEnvExt::JNIEnvExt(Thread* self, JavaVMExt* vm)
: ...... {
functions = unchecked_functions = &gJniNativeInterface;
......
}
这个函数定义在文件art/runtime/jni_internal.cc中。
JNIEnvExt类的构造函数将父类JNIEnv的成员变量functions初始化为全局变量gJniNativeInterface。也就是说,JNI函数表实际是由全局变量gJniNativeInterface来描述的。
全局变量gJniNativeInterface的定义如下所示:
const JNINativeInterface gJniNativeInterface = {
NULL, // reserved0.
NULL, // reserved1.
NULL, // reserved2.
NULL, // reserved3.
JNI::GetVersion,
......
JNI::FindClass,
......
JNI::GetStaticMethodID,
......
JNI::CallStaticVoidMethod,
......
};
这个全局变量定义在文件art/runtime/jni_internal.cc中。
从这里可以看出,JNI函数表实际上是由JNI类的静态成员函数组成的。例如,JNI函数GetVersion是由JNI类的静态成员函数GetVersion来实现的。理解了这一点之后,我们就轻松地知道同接下来我们要分析的JNI接口FindClass和GetStaticMethodID分别是由JNI类的静态成员函数FindClass和GetStaticMethodID来实现的。事实上,如果读者看过Dalvik虚拟机的启动过程分析这篇文章,那么对上述的JNI接口定义是一目了然的。
JNI类的静态成员函数FindClass的实现如下所示:
class JNI {
public:
......
static jclass FindClass(JNIEnv* env, const char* name) {
CHECK_NON_NULL_ARGUMENT(FindClass, name);
Runtime* runtime = Runtime::Current();
ClassLinker* class_linker = runtime->GetClassLinker();
std::string descriptor(NormalizeJniClassDescriptor(name));
ScopedObjectAccess soa(env);
Class* c = NULL;
if (runtime->IsStarted()) {
ClassLoader* cl = GetClassLoader(soa);
c = class_linker->FindClass(descriptor.c_str(), cl);
} else {
c = class_linker->FindSystemClass(descriptor.c_str());
}
return soa.AddLocalReference<jclass>(c);
}
......
};
这个函数定义在文件art/runtime/jni_internal.cc中。
在ART虚拟机进程中,存在着一个Runtime单例,用来描述ART运行时。通过调用Runtime类的静态成员函数Current可以获得上述Runtime单例。获得了这个单例之后,就可以调用它的成员函数GetClassLinker来获得一个ClassLinker对象。从前面Android运行时ART加载OAT文件的过程分析一文可以知道。上述ClassLinker对象是在创建ART虚拟机的过程中创建的,用来加载类以及链接类方法。
JNI类的静态成员函数FindClass首先是判断ART运行时是否已经启动起来。如果已经启动,那么就通过调用函数GetClassLoader来获得当前线程所关联的ClassLoader,并且以此为参数,调用前面获得的ClassLinker对象的成员函数FindClass来加载由参数name指定的类。一般来说,当前线程所关联的ClassLoader就是当前正在执行的类方法所关联的ClassLoader,即用来加载当前正在执行的类的ClassLoader。如果ART虚拟机还没有开始执行类方法,就像我们现在这个场景,那么当前线程所关联的ClassLoader实际上就系统类加载器,即SystemClassLoader。
如果ART运行时还没有启动,那么这时候只可以加载系统类。这个通过前面获得的ClassLinker对象的成员函数FindSystemClass来实现的。在我们这个场景中,ART运行时已经启动,因此,接下来我们就继续分析ClassLinker类的成员函数FindClass的实现。
ClassLinker类的成员函数FindClass的实现如下所示:
mirror::Class* ClassLinker::FindClass(const char* descriptor, mirror::ClassLoader* class_loader) {
......
Thread* self = Thread::Current();
......
// Find the class in the loaded classes table.
mirror::Class* klass = LookupClass(descriptor, class_loader);
if (klass != NULL) {
return EnsureResolved(self, klass);
}
// Class is not yet loaded.
if (descriptor[0] == '[') {
......
} else if (class_loader == NULL) {
DexFile::ClassPathEntry pair = DexFile::FindInClassPath(descriptor, boot_class_path_);
if (pair.second != NULL) {
return DefineClass(descriptor, NULL, *pair.first, *pair.second);
}
} else if (Runtime::Current()->UseCompileTimeClassPath()) {
......
} else {
ScopedObjectAccessUnchecked soa(self->GetJniEnv());
ScopedLocalRef<jobject> class_loader_object(soa.Env(),
soa.AddLocalReference<jobject>(class_loader));
std::string class_name_string(DescriptorToDot(descriptor));
ScopedLocalRef<jobject> result(soa.Env(), NULL);
{
ScopedThreadStateChange tsc(self, kNative);
ScopedLocalRef<jobject> class_name_object(soa.Env(),
soa.Env()->NewStringUTF(class_name_string.c_str()));
if (class_name_object.get() == NULL) {
return NULL;
}
CHECK(class_loader_object.get() != NULL);
result.reset(soa.Env()->CallObjectMethod(class_loader_object.get(),
WellKnownClasses::java_lang_ClassLoader_loadClass,
class_name_object.get()));
}
if (soa.Self()->IsExceptionPending()) {
// If the ClassLoader threw, pass that exception up.
return NULL;
} else if (result.get() == NULL) {
// broken loader - throw NPE to be compatible with Dalvik
ThrowNullPointerException(NULL, StringPrintf("ClassLoader.loadClass returned null for %s",
class_name_string.c_str()).c_str());
return NULL;
} else {
// success, return mirror::Class*
return soa.Decode<mirror::Class*>(result.get());
}
}
ThrowNoClassDefFoundError("Class %s not found", PrintableString(descriptor).c_str());
return NULL;
}
这个函数定义在文件art/runtime/class_linker.cc中。
参数descriptor指向的是要加载的类的签名,而参数class_loader指向的是一个类加载器,我们假设它的值不为空,并且指向系统类加载器。
ClassLinker类的成员函数FindClass首先是调用另外一个成员函数LookupClass来检查参数descriptor指定的类是否已经被加载过。如果是的话,那么ClassLinker类的成员函数LookupClass就会返回一个对应的Class对象,这个Class对象接着就会返回给调用者,表示加载已经完成。
如果参数descriptor指定的类还没有被加载过,这时候主要就是要看参数class_loader的值了。如果参数class_loader的值等于NULL,那么就需要调用DexFile类的静态FindInClassPath来在系统启动类路径寻找对应的类。一旦寻找到,那么就会获得包含目标类的DEX文件,因此接下来就调用ClassLinker类的另外一个成员函数DefineClass从获得的DEX文件中加载参数descriptor指定的类了。
如果参数class_loader的值不等于NULL,也就是说ClassLinker类的成员函数FindClass的调用者指定了类加载器,那么就通过该类加载器来加载参数descriptor指定的类。每一个类加载器在Java层都对应有一个java.lang.ClassLoader对象。通过调用这个java.lang.ClassLoader类的成员函数loadClass即可加载指定的类。在我们这个场景中,上述的java.lang.ClassLoader类是一个系统类加载器,它负责加载系统类。而我们当前要加载的类为com.android.internal.os.ZygoteInit,它属于一个系统类。
系统类加载器在加载系统类实际上也是通过JNI方法调用ClassLinker类的成员函数FindClass来实现的。只不过这时候传进来的参数class_loader是一个NULL值。这样,ClassLinker类的成员函数FindClass就会在系统启动类路径中寻找参数descriptor指定的类可以在哪一个DEX文件加载,这是通过调用DexFile类的静态成员函数FindInClassPath来实现的。
所谓的系统启动类路径,其实就是一系列指定的由系统提供的DEX文件,这些DEX文件保存在ClassLinker类的成员变量boot_class_path_描述的一个向量中。那么问题就来了,这些DEX文件是怎么来的呢?我们知道,在ART运行时中,我们使用的是OAT文件。如果看过前面Android运行时ART加载OAT文件的过程分析这篇文章,就会很容易知道,OAT文件里面包含有DEX文件。而且ART运行时在启动的时候,会加载一个名称为system@framework@boot.art@classes.oat的OAT文件。这个OAT文件包含有多个DEX文件,每一个DEX文件都是一个系统启动类路径,它们会被添加到ClassLinker类的成员变量boot_class_path_描述的向量中去。
这里调用DexFile类的静态成员函数FindInClassPath,实际要完成的工作就是从ClassLinker类的成员变量boot_class_path_描述的一系列的DEX文件中检查哪一个DEX文件包含有参数descriptor指定的类。这可以通过解析DEX文件来实现,关于DEX文件的格式,可以参考官方文档:http://source.android.com/tech/dalvik/index.html。
知道了参数descriptor指定的类定义在哪一个DEX文件之后,就可以通过ClassLinker类的另外一个成员函数DefineClass来从中加载它了。接下来,我们就继续分析ClassLinker类的成员函数DefineClass的实现,如下所示:
mirror::Class* ClassLinker::DefineClass(const char* descriptor,
mirror::ClassLoader* class_loader,
const DexFile& dex_file,
const DexFile::ClassDef& dex_class_def) {
Thread* self = Thread::Current();
SirtRef<mirror::Class> klass(self, NULL);
// Load the class from the dex file.
if (UNLIKELY(!init_done_)) {
// finish up init of hand crafted class_roots_
if (strcmp(descriptor, "Ljava/lang/Object;") == 0) {
klass.reset(GetClassRoot(kJavaLangObject));
} else if (strcmp(descriptor, "Ljava/lang/Class;") == 0) {
klass.reset(GetClassRoot(kJavaLangClass));
} else if (strcmp(descriptor, "Ljava/lang/String;") == 0) {
klass.reset(GetClassRoot(kJavaLangString));
} else if (strcmp(descriptor, "Ljava/lang/DexCache;") == 0) {
klass.reset(GetClassRoot(kJavaLangDexCache));
} else if (strcmp(descriptor, "Ljava/lang/reflect/ArtField;") == 0) {
klass.reset(GetClassRoot(kJavaLangReflectArtField));
} else if (strcmp(descriptor, "Ljava/lang/reflect/ArtMethod;") == 0) {
klass.reset(GetClassRoot(kJavaLangReflectArtMethod));
} else {
klass.reset(AllocClass(self, SizeOfClass(dex_file, dex_class_def)));
}
} else {
klass.reset(AllocClass(self, SizeOfClass(dex_file, dex_class_def)));
}
......
LoadClass(dex_file, dex_class_def, klass, class_loader);
......
{
// Add the newly loaded class to the loaded classes table.
mirror::Class* existing = InsertClass(descriptor, klass.get(), Hash(descriptor));
if (existing != NULL) {
// We failed to insert because we raced with another thread. Calling EnsureResolved may cause
// this thread to block.
return EnsureResolved(self, existing);
}
}
......
if (!LinkClass(klass, NULL, self)) {
// Linking failed.
klass->SetStatus(mirror::Class::kStatusError, self);
return NULL;
}
......
return klass.get();
}
这个函数定义在文件art/runtime/class_linker.cc中。
ClassLinker类有一个类型为bool的成员变量initdone,用来表示ClassLinker是否已经初始化完成。ClassLinker在创建的时候,有一个初始化过程,用来创建一些内部类。这些内部类要么是手动创建的,要么是从Image空间获得的。关于ART虚拟机的Image空间,我们在后面分析ART垃圾收集机制的文章中再详细分析。
调用ClassLinker类的成员函数DefineClass的时候,如果ClassLinker正处于初始化过程,即其成员变量init_done_的值等于false,并且参数descriptor描述的是特定的内部类,那么就将本地变量klass指向它们,其余情况则会通过成员函数AllocClass为其分配存储空间,以便后面通过成员函数LoadClass进行初始化。
ClassLinker类的成员函数LoadClass用来从指定的DEX文件中加载指定的类。指定的类从DEX文件中加载完成后,需要通过另外一个成员函数InsertClass添加到ClassLinker的已加载类列表中去。如果指定的类之前已经加载过,即调用成员函数InsertClass得到的返回值不等于空,那么就说明有另外的一个线程也正在加载指定的类。这时候就需要调用成员函数EnsureResolved来保证(等待)该类已经加载并且解析完成。另一方面,如果没有其它线程加载指定的类,那么当前线程从指定的DEX文件加载完成指定的类后,还需要调用成员函数LinkClass来对加载后的类进行解析。最后,一个类型为Class的对象就可以返回给调用者了,用来表示一个已经加载和解析完成的类。
接下来,我们主要分析ClassLinker类的成员函数LoadClass的实现,以便可以了解类的加载过程。
ClassLinker类的成员函数LoadClass的实现如下所示:
void ClassLinker::LoadClass(const DexFile& dex_file,
const DexFile::ClassDef& dex_class_def,
SirtRef<mirror::Class>& klass,
mirror::ClassLoader* class_loader) {
......
klass->SetClassLoader(class_loader);
......
klass->SetDexClassDefIndex(dex_file.GetIndexForClassDef(dex_class_def));
.....
// Load fields fields.
const byte* class_data = dex_file.GetClassData(dex_class_def);
......
ClassDataItemIterator it(dex_file, class_data);
Thread* self = Thread::Current();
if (it.NumStaticFields() != 0) {
mirror::ObjectArray<mirror::ArtField>* statics = AllocArtFieldArray(self, it.NumStaticFields());
......
klass->SetSFields(statics);
}
if (it.NumInstanceFields() != 0) {
mirror::ObjectArray<mirror::ArtField>* fields =
AllocArtFieldArray(self, it.NumInstanceFields());
......
klass->SetIFields(fields);
}
for (size_t i = 0; it.HasNextStaticField(); i++, it.Next()) {
SirtRef<mirror::ArtField> sfield(self, AllocArtField(self));
......
klass->SetStaticField(i, sfield.get());
LoadField(dex_file, it, klass, sfield);
}
for (size_t i = 0; it.HasNextInstanceField(); i++, it.Next()) {
SirtRef<mirror::ArtField> ifield(self, AllocArtField(self));
......
klass->SetInstanceField(i, ifield.get());
LoadField(dex_file, it, klass, ifield);
}
UniquePtr<const OatFile::OatClass> oat_class;
if (Runtime::Current()->IsStarted() && !Runtime::Current()->UseCompileTimeClassPath()) {
oat_class.reset(GetOatClass(dex_file, klass->GetDexClassDefIndex()));
}
// Load methods.
if (it.NumDirectMethods() != 0) {
// TODO: append direct methods to class object
mirror::ObjectArray<mirror::ArtMethod>* directs =
AllocArtMethodArray(self, it.NumDirectMethods());
......
klass->SetDirectMethods(directs);
}
if (it.NumVirtualMethods() != 0) {
// TODO: append direct methods to class object
mirror::ObjectArray<mirror::ArtMethod>* virtuals =
AllocArtMethodArray(self, it.NumVirtualMethods());
......
klass->SetVirtualMethods(virtuals);
}
size_t class_def_method_index = 0;
for (size_t i = 0; it.HasNextDirectMethod(); i++, it.Next()) {
SirtRef<mirror::ArtMethod> method(self, LoadMethod(self, dex_file, it, klass));
......
klass->SetDirectMethod(i, method.get());
if (oat_class.get() != NULL) {
LinkCode(method, oat_class.get(), class_def_method_index);
}
method->SetMethodIndex(class_def_method_index);
class_def_method_index++;
}
for (size_t i = 0; it.HasNextVirtualMethod(); i++, it.Next()) {
SirtRef<mirror::ArtMethod> method(self, LoadMethod(self, dex_file, it, klass));
......
klass->SetVirtualMethod(i, method.get());
......
if (oat_class.get() != NULL) {
LinkCode(method, oat_class.get(), class_def_method_index);
}
class_def_method_index++;
}
......
}
这个函数定义在文件art/runtime/class_linker.cc中。
我们首先要明确一下各个参数的含义:
dex_file: 类型为DexFile,描述要加载的类所在的DEX文件。
dex_class_def: 类型为ClassDef,描述要加载的类在DEX文件里面的信息。
klass: 类型为Class,描述加载完成的类。
class_loader: 类型为ClassLoader,描述所使用的类加载器。
总的来说,ClassLinker类的成员函数LoadClass的任务就是要用dex_file、dex_class_def、class_loader三个参数包含的相关信息设置到参数klass描述的Class对象去,以便可以得到一个完整的已加载类信息。
ClassLinker类的成员函数LoadClass主要完成的工作如下所示:
1. 将参数class_loader描述的ClassLoader设置到klass描述的Class对象中去,即给每一个已加载类关联一个类加载器。
2. 通过DexFile类的成员函数GetIndexForClassDef获得正在加载的类在DEX文件中的类索引号,并且设置到klass描述的Class对象中去。这个类索引号是一个很重要的信息,因为我们需要通过类索引号在相应的OAT文件找到一个OatClass结构体。有了这个OatClass结构体之后,我们才可以找到类方法对应的本地机器指令。具体可以参考前面图1和Android运行时ART加载OAT文件的过程分析一文。
3. 从参数dex_file描述的DEX文件中获得正在加载的类的静态成员变量和实例成员变量个数,并且为每一个静态成员变量和实例成员变量都分配一个ArtField对象,接着通过ClassLinker类的成员函数LoadField对这些ArtField对象进行初始化。初始好得到的ArtField对象全部保存在klass描述的Class对象中。
4. 调用ClassLinker类的成员函数GetOatClass,从相应的OAT文件中找到与正在加载的类对应的一个OatClass结构体oat_class。这需要利用到上面提到的DEX类索引号,这是因为DEX类和OAT类根据索引号存在一一对应关系。这一点可以参考图1和Android运行时ART加载OAT文件的过程分析一文。
从参数dex_file描述的DEX文件中获得正在加载的类的直接成员函数和虚拟成员函数个数,并且为每一个直接成员函数和虚拟成员函数都分配一个ArtMethod对象,接着通过ClassLinker类的成员函数LoadMethod对这些ArtMethod对象进行初始化。初始好得到的ArtMethod对象全部保存在klass描述的Class对象中。
每一个直接成员函数和虚拟成员函数都对应有一个函数索引号。根据这个函数索引号可以在第4步得到的OatClass结构体中找到对应的本地机器指令,具体可以参考前面图1和Android运行时ART加载OAT文件的过程分析一文。所有与这些成员函数关联的本地机器指令信息通过全局函数LinkCode设置到klass描述的Class对象中。
总结来说,参数klass描述的Class对象包含了一系列的ArtField对象和ArtMethod对象,其中,ArtField对象用来描述成员变量信息,而ArtMethod用来描述成员函数信息。
接下来,我们继续分析全局函数LinkCode的实现,以便可以了解如何在一个OAT文件中找到一个DEX类方法的本地机器指令。
函数LinkCode的实现如下所示:
static void LinkCode(SirtRef<mirror::ArtMethod>& method, const OatFile::OatClass* oat_class,
uint32_t method_index)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// Method shouldn't have already been linked.
DCHECK(method->GetEntryPointFromCompiledCode() == NULL);
// Every kind of method should at least get an invoke stub from the oat_method.
// non-abstract methods also get their code pointers.
const OatFile::OatMethod oat_method = oat_class->GetOatMethod(method_index);
oat_method.LinkMethod(method.get());
// Install entry point from interpreter.
Runtime* runtime = Runtime::Current();
bool enter_interpreter = NeedsInterpreter(method.get(), method->GetEntryPointFromCompiledCode());
if (enter_interpreter) {
method->SetEntryPointFromInterpreter(interpreter::artInterpreterToInterpreterBridge);
} else {
method->SetEntryPointFromInterpreter(artInterpreterToCompiledCodeBridge);
}
if (method->IsAbstract()) {
method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
return;
}
if (method->IsStatic() && !method->IsConstructor()) {
// For static methods excluding the class initializer, install the trampoline.
// It will be replaced by the proper entry point by ClassLinker::FixupStaticTrampolines
// after initializing class (see ClassLinker::InitializeClass method).
method->SetEntryPointFromCompiledCode(GetResolutionTrampoline(runtime->GetClassLinker()));
} else if (enter_interpreter) {
// Set entry point from compiled code if there's no code or in interpreter only mode.
method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
}
if (method->IsNative()) {
// Unregistering restores the dlsym lookup stub.
method->UnregisterNative(Thread::Current());
}
// Allow instrumentation its chance to hijack code.
runtime->GetInstrumentation()->UpdateMethodsCode(method.get(),
method->GetEntryPointFromCompiledCode());
}
这个函数定义在文件art/runtime/class_linker.cc中。
参数method表示要设置本地机器指令的类方法,参数oat_class表示类方法method在OAT文件中对应的OatClass结构体,参数method_index表示类方法method的索引号。
通过参数method_index描述的索引号可以在oat_class表示的OatClass结构体中找到一个OatMethod结构体oat_method。这个OatMethod结构描述了类方法method的本地机器指令相关信息,通过调用它的成员函数LinkMethod可以将这些信息设置到参数method描述的ArtMethod对象中去。如下所示:
const void* OatFile::OatMethod::GetCode() const {
return GetOatPointer<const void*>(code_offset_);
}
......
void OatFile::OatMethod::LinkMethod(mirror::ArtMethod* method) const {
CHECK(method != NULL);
method->SetEntryPointFromCompiledCode(GetCode());
method->SetFrameSizeInBytes(frame_size_in_bytes_);
method->SetCoreSpillMask(core_spill_mask_);
method->SetFpSpillMask(fp_spill_mask_);
method->SetMappingTable(GetMappingTable());
method->SetVmapTable(GetVmapTable());
method->SetNativeGcMap(GetNativeGcMap()); // Used by native methods in work around JNI mode.
}
这个函数定义在文件art/runtime/oat_file.cc中。
其中,最重要的就是通过OatMethod类的成员函数GetCode获得OatMethod结构体中的code_offset_字段,并且通过调用ArtMethod类的成员函数SetEntryPointFromCompiledCode设置到参数method描述的ArtMethod对象中去。OatMethod结构体中的code_offset_字段指向的是一个本地机器指令函数,这个本地机器指令函数正是通过翻译参数method描述的类方法的DEX字节码得到的。
回到函数LinkCode中,它接着调用另外一个全局函数NeedsInterpreter检查参数method描述的类方法是否需要通过解释器执行,它的实现如下所示:
// Returns true if the method must run with interpreter, false otherwise.
static bool NeedsInterpreter(const mirror::ArtMethod* method, const void* code) {
if (code == NULL) {
// No code: need interpreter.
return true;
}
......
// If interpreter mode is enabled, every method (except native and proxy) must
// be run with interpreter.
return Runtime::Current()->GetInstrumentation()->InterpretOnly() &&
!method->IsNative() && !method->IsProxyMethod();
}
这个函数定义在文件art/runtime/class_linker.cc中。
在以下两种情况下,一个类方法需要通过解释器来执行:
1. 没有对应的本地机器指令,即参数code的值等于NULL。
2. ART虚拟机运行在解释模式中,并且类方法不是JNI方法,并且也不是代理方法。
调用Runtime类的静态成员函数Current获得的是描述ART运行时的一个Runtime对象。调用这个Runtime对象的成员函数GetInstrumentation获得的是一个Instrumentation对象。这个Instrumentation对象是用来调试ART运行时的,通过调用它的成员函数InterpretOnly可以知道ART虚拟机是否运行在解释模式中。
因为JNI方法是没有对应的DEX字节码的,因此即使ART虚拟机运行在解释模式中,JNI方法也不能通过解释器来执行。至于代理方法,由于是动态生成的(没有对应的DEX字节码),因此即使ART虚拟机运行在解释模式中,它们也不通过解释器来执行(这一点猜测的,还没有确认)。
回到函数LinkCode中,如果调用函数NeedsInterpreter得到的返回值enter_interpreter等于true,那么就意味着参数method描述的类方法需要通过解释器来执行,这时候就将函数artInterpreterToInterpreterBridge设置为解释器执行该类方法的入口点。否则的话,就将另外一个函数artInterpreterToCompiledCodeBridge设置为解释器执行该类方法的入口点。
为什么我们需要为类方法设置解释器入口点呢?根据前面的分析可以知道,在ART虚拟机中,并不是所有的类方法都是有对应的本地机器指令的,并且即使一个类方法有对应的本地机器指令,当ART虚拟机以解释模式运行时,它也需要通过解释器来执行。当以解释器执行的类方法在执行的过程中调用了其它的类方法时,解释器就需要进一步知道被调用的类方法是应用以解释方式执行,还是本地机器指令方法执行。为了能够进行统一处理,就给每一个类方法都设置一个解释器入口点。需要通过解释执行的类方法的解释器入口点函数是artInterpreterToInterpreterBridge,它会继续通过解释器来执行该类方法。需要通过本地机器指令执行的类方法的解释器入口点函数是artInterpreterToCompiledCodeBridge,它会间接地调用该类方法的本地机器指令。
函数LinkCode继续往下执行,判断参数method描述的类方法是否是一个抽象方法。抽象方法声明类中是没有实现的,必须要由子类实现。因此抽象方法在声明类中是没有对应的本地机器指令的,它们必须要通过解释器来执行。不过,为了能够进行统一处理,我们仍然假装抽象方法有对应的本地机器指令函数,只不过这个本地机器指令函数被设置为GetCompiledCodeToInterpreterBridge。当函数GetCompiledCodeToInterpreterBridge被调用时,就会自动进入到解释器中去。
对于非抽象方法,函数LinkCode还要继续往下处理。到这里有一点是需要注意的,前面通过调用OatMethod类的成员函数LinkMethod,我们已经设置好参数method描述的类方法的本地机器指令了。但是,在以下两种情况下,我们需要进行调整:
1. 当参数method描述的类方法是一个非类静态初始化函数(class initializer)的静态方法时,我们不能直接执行翻译其DEX字节码得到的本地机器指令。这是因为类静态方法可以在不创建类对象的前提下执行。这意味着一个类静态方法在执行的时候,对应的类可能还没有初始化好。这时候我们就需要先将对应的类初始化好,再执行相应的静态方法。为了能够做到这一点。我们就先调用GetResolutionTrampoline函数得到一个Tampoline函数,接着将这个Trampoline函数作为静态方法的本地机器指令。这样如果类静态方法在对应的类初始化前被调用,就会触发上述的Trampoline函数被执行。而当上述Trampoline函数执行时,它们先初始化好对应的类,再调用原来的类静态方法对应的本地机器指令。按照代码中的注释,当一个类初始化完成之后,就可以调用函数ClassLinker::FixupStaticTrampolines来修复该类的静态成员函数的本地机器指令,也是通过翻译DEX字节码得到的本地机器指令。这里需要注意的是,为什么类静态初始化函数不需要按照其它的类静态方法一样设置Tampoline函数呢?这是因为类静态初始化函数是一定保证是在类初始化过程中执行的。
函数LinkCode接下来继续判断参数method描述的类方法是否是一个JNI方法。如果是的话,那么就调用ArtMethod类的成员函数UnregisterNative来初始化它的JNI方法调用接口。ArtMethod类的成员函数UnregisterNative的实现如下所示:
void ArtMethod::UnregisterNative(Thread* self) {
CHECK(IsNative()) << PrettyMethod(this);
// restore stub to lookup native pointer via dlsym
RegisterNative(self, GetJniDlsymLookupStub());
}
这个函数定义在文件runtime/mirror/art_method.cc中。
ArtMethod类的成员函数UnregisterNative实际上就是将一个JNI方法的初始化入口设置为通过调用函数GetJniDlsymLookupStub获得的一个Stub。这个Stub的作用是,当一个JNI方法被调用时,如果还没有显示地注册有Native函数,那么它就会自动从已加载的SO文件查找是否存在一个对应的Native函数。如果存在的话,就将它注册为JNI方法的Native函数,并且执行它。这就是隐式的JNI方法注册。
回到函数LinkCode,它最后调用Instrumentation类的成员函数UpdateMethodsCode检查是否要进一步修改参数method描述的类方法的本地机器指令入口,它的实现如下所示:
void Instrumentation::UpdateMethodsCode(mirror::ArtMethod* method, const void* code) const {
if (LIKELY(!instrumentation_stubs_installed_)) {
method->SetEntryPointFromCompiledCode(code);
} else {
if (!interpreter_stubs_installed_ || method->IsNative()) {
method->SetEntryPointFromCompiledCode(GetQuickInstrumentationEntryPoint());
} else {
method->SetEntryPointFromCompiledCode(GetCompiledCodeToInterpreterBridge());
}
}
}
这个函数定义在文件art/runtime/instrumentation.cc中。
Instrumentation类是用来调用ART运行时的。例如,当我们需要监控类方法的调用时,就可以往Instrumentation注册一些Listener。这样当类方法调用时,这些注册的Listener就会得到回调。当Instrumentation注册有相应的Listener时,它的成员变量instrumentation_stubs_installed_的值就会等于true。
因此,当Instrumentation类的成员变量instrumentation_stubs_installed_的值等于true时,我们需要使用一个监控函数来替换掉类方法原来的本地机器指令。这样当类方法被调用时,监控函数就获得控制权,它可以在调用原来的本地机器指令前后,向注册的Listener发出通知。
对于JNI方法,我们通过调用函数GetQuickInstrumentationEntryPoint获得的函数作为其监控函数;而对其它的类方法,我们通过调用函数GetCompiledCodeToInterpreterBridge获得的函数作为其监控函数。
另一方面,如果没有Listener注册到Instrumentation中,即它的成员变量instrumentation_stubs_installed_的值等于false,那么Instrumentation类的成员函UpdateMethodsCode就会使用参数code描述的本地机器指令作为参数method描述的类方法的本地机器指令入口。参数code描述的本地机器指一般就是翻译类方法的DEX字节码得到的本地机器指令了。实际上是相当于没有修改类方法的本地机器指令入口。
这样,一个类的加载过程就完成了。加载完成后,得到的是一个Class对象。这个Class对象关联有一系列的ArtField对象和ArtMethod对象。其中,ArtField对象描述的是成员变量,而ArtMethod对象描述的是成员函数。对于每一个ArtMethod对象,它都有一个解释器入口点和一个本地机器指令入口点。这样,无论一个类方法是通过解释器执行,还是直接以本地机器指令执行,我们都可以以统一的方式来进行调用。同时,理解了上述的类加载过程后,我们就可以知道,我们在Native层通过JNI接口FindClass查找或者加载类时,得到的一个不透明的jclass值,实际上指向的是一个Class对象。
有了类加载过程的知识后,接下来我们再继续分析类方法的查找过程,也就是分析JNI接口GetStaticMethodID的实现。按照前面的分析,JNI接口GetStaticMethodID是由JNI类的静态成员函数GetStaticMethodID实现的。因此,接下来我们就开始分析JNI类的静态成员函数GetStaticMethodID的实现。
JNI类的静态成员函数GetStaticMethodID的实现如下所示:
class JNI {
public:
......
static jmethodID GetStaticMethodID(JNIEnv* env, jclass java_class, const char* name,
const char* sig) {
CHECK_NON_NULL_ARGUMENT(GetStaticMethodID, java_class);
CHECK_NON_NULL_ARGUMENT(GetStaticMethodID, name);
CHECK_NON_NULL_ARGUMENT(GetStaticMethodID, sig);
ScopedObjectAccess soa(env);
return FindMethodID(soa, java_class, name, sig, true);
}
......
};
这个函数定义在文件art/runtime/jni_internal.cc中。
参数name和sig描述的分别是要查找的类方法的名称和签名,而参数java_class的是对应的类。参数java_class的类型是jclass,从前面类加载过程的分析可以知道,它实际上指向的是一个Class对象。
JNI类的静态成员函数GetStaticMethodID通过调用一个全局函数FindMethodID来查找指定的类,后者的实现如下所示:
static jmethodID FindMethodID(ScopedObjectAccess& soa, jclass jni_class,
const char* name, const char* sig, bool is_static)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
Class* c = soa.Decode<Class*>(jni_class);
if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(c, true, true)) {
return NULL;
}
ArtMethod* method = NULL;
if (is_static) {
method = c->FindDirectMethod(name, sig);
} else {
method = c->FindVirtualMethod(name, sig);
if (method == NULL) {
// No virtual method matching the signature. Search declared
// private methods and constructors.
method = c->FindDeclaredDirectMethod(name, sig);
}
}
if (method == NULL || method->IsStatic() != is_static) {
ThrowNoSuchMethodError(soa, c, name, sig, is_static ? "static" : "non-static");
return NULL;
}
return soa.EncodeMethod(method);
}
这个函数定义在文件art/runtime/jni_internal.cc。
函数FindMethodID的执行过程如下所示:
1. 将参数jni_class的值转换为一个Class指针c,因此就可以得到一个Class对象,并且通过ClassLinker类的成员函数EnsureInitialized确保该Class对象描述的类已经初始化。
2. Class对象c描述的类在加载的过程中,经过解析已经关联上一系列的成员函数。这些成员函数可以分为两类:Direct和Virtual。Direct类的成员函数包括所有的静态成员函数、私有成员函数和构造函数,而Virtual则包括所有的虚成员函数。因此:
2.1. 当参数is_static的值等于true时,那么就表示要查找的是静态成员函数,这时候就在Class对象c描述的类的关联的Direct成员函数列表中查找参数name和sig对应的成员函数。这是通过调用Class类的成员函数FindDirectMethod来实现的。
2.2. 当参数is_static的值不等于true时,那么就表示要查找的是虚拟成员函数或者非静态的Direct成员函数,这时候先在Class对象c描述的类的关联的Virtual成员函数列表中查找参数name和sig对应的成员函数。这是通过调用Class类的成员函数FindVirtualMethod来实现的。如果找不到对应的虚拟成员函数,那么再在Class对象c描述的类的关联的Direct成员函数列表中查找参数name和sig对应的成员函数。
3. 经过前面的查找过程,如果都不能在Class对象c描述的类中找到与参数name和sig对应的成员函数,那么就抛出一个NoSuchMethodError异常。否则的话,就将查找得到的ArtMethod对象封装成一个jmethodID值返回给调用者。
也就是说,我们通过调用JNI接口GetStaticMethodID获得的不透明jmethodID值指向的实际上是一个ArtMethod对象。得益于前面的类加载过程,当我们获得了一个ArtMethod对象之后,就可以轻松地得到它的本地机器指令入口,进而对它进行执行。
这样,我们就分析完成类方法的查找过程了。在接下来的一篇文章中,我们将继续分析类方法的本地机器指令的调用过程。通过对类方法的本地机器指令的调用过程的理解,可以进一步理解ART虚拟机的运行原理。敬请关注!更多信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8