android虚拟机(二)

android虚拟机(二)

ART虚拟机加载流程

summary up https://blog.csdn.net/luoshengyang/article/details/39533503

需要先了解:
ART中几个组件
java中的ClassLoader

  AndroidRuntime类会对mJavaVM和JNIEnv进行创建,利用JNI接口,就可以获取到mJavaVM的入口类,并通过这个入口进入虚拟机内部,开始运行。
  这个入口类是com.android.internal.os.ZygoteInit,通过JNI提供的FindClass和GetStaticMethodID函数就能获取到入口类的静态成员函数main,在由JNI提供的CallStaticVoidMethod就可以调用获取到的main函数,进入虚拟机内部。

JNI类的静态成员函数FindClass:

ClassLoader
Handle<mirror::ClassLoader>class_loader(hs.NewHandle(GetClassLoader(soa)));

need to get the instance of Runtime from the current Thread,then we can get the ClassLinker by Runtime->getClassLinker.

ClassLinker
1⃣️->FindClass
2⃣️->FindSystemClass

FindClass:

1
2
3
4
5
6
7
8
else if (class_loader.Get() == nullptr) {
// The boot class loader, search the boot class path.
ClassPathEntry pair = FindInClassPath(descriptor, boot_class_path_);
if (pair.second != nullptr) {
return DefineClass(descriptor, NullHandle<mirror::ClassLoader>(), *pair.first, *pair.second);
} else {
...

  1. 如果要找的类是基础类型,则直接调用FindPrimitiveClass查找基础类型;如果不是则在传入的ClassLoader的已加载类表中查找类(首先会判断ClassLaoder是否为null,如果为null,就在boot_class_table_中查找,否则就在ClassLoader自己的ClassTable中查找),如果找到,确保类已经被解析并返回
  2. 如果在传入的ClassLoader的已加载类表中没有找到类,则首先判断ClassLoader是否为空,如果为空,在boot_class_path_(系统启动类路径)中查找,如果不为空,则调用FindClassInPathClassLoader在传入的ClassLoader以及它的各级parent中查找类
  3. 如果仍然没有找到,则调用传入的ClassLoader的loadClass在传入的ClassLoader中查找,找到返回,否则抛出异常

copy from here

DefineClass:

1
2
3
4
5
mirror::Class* ClassLinker::DefineClass(const char* descriptor,
Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
const DexFile::ClassDef& dex_class_def){
...
  1. ClassLinker是否已经初始化完成(在初始化时会创建一些内部类)
  2. 未初始化完成时,loadClass内部类或为类allocclass分配空间再loadClass
  3. LoadClass从dex中加载类,并通过InsertClass添加到已加载类列表中(在其他线程同时在加载该类时,通过EnsureResolved确保同步)
  4. LinkClass来对加载后的类进行解析,类解析完后得到一个Class对象

LoadClass:

1
2
3
4
5
void ClassLinker::LoadClass(const DexFile& dex_file,  
const DexFile::ClassDef& dex_class_def,
SirtRef<mirror::Class>& klass,
mirror::ClassLoader* class_loader) {
...
参数 描述
dex_file 类型为DexFile,描述要加载的类所在的DEX文件
dex_class_def 类型为ClassDef,描述要加载的类在DEX文件里面的信息
klass 类型为Class,描述加载完成的类
class_loader 类型为ClassLoader,描述所使用的类加载器
  1. 将参数class_loader描述的ClassLoader设置到klass描述的Class对象中去,即给每一个已加载类关联一个类加载器。

  2. 通过DexFile类的成员函数GetIndexForClassDef获得正在加载的类在DEX文件中的类索引号,并且设置到klass描述的Class对象中去。这个类索引号是一个很重要的信息,因为我们需要通过类索引号在相应的OAT文件找到一个OatClass结构体。有了这个OatClass结构体之后,我们才可以找到类方法对应的本地机器指令。

  3. 从参数dex_file描述的DEX文件中获得正在加载的类的静态成员变量和实例成员变量个数,并且为每一个静态成员变量和实例成员变量都分配一个ArtField对象,接着通过ClassLinker类的成员函数LoadField对这些ArtField对象进行初始化。初始好得到的ArtField对象全部保存在klass描述的Class对象中。

  4. 调用ClassLinker类的成员函数GetOatClass,从相应的OAT文件中找到与正在加载的类对应的一个OatClass结构体oat_class。这需要利用到上面提到的DEX类索引号,这是因为DEX类和OAT类根据索引号存在一一对应关系。

  5. 从参数dex_file描述的DEX文件中获得正在加载的类的直接成员函数和虚拟成员函数个数,并且为每一个直接成员函数和虚拟成员函数都分配一个ArtMethod对象,接着通过ClassLinker类的成员函数LoadMethod对这些ArtMethod对象进行初始化。初始好得到的ArtMethod对象全部保存在klass描述的Class对象中。

  6. 每一个直接成员函数和虚拟成员函数都对应有一个函数索引号。根据这个函数索引号可以在第4步得到的OatClass结构体中找到对应的本地机器指令。所有与这些成员函数关联的本地机器指令信息通过全局函数LinkCode设置到klass描述的Class对象中。

LinkCode:

1
2
3
4
static void LinkCode(SirtRef<mirror::ArtMethod>& method, const OatFile::OatClass* oat_class,  
uint32_t method_index)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
...
参数 描述
method 要设置本地机器指令的类方法
oat_class 类方法method在OAT文件中对应的OatClass结构体
method_index 类方法method的索引号

在OatClass中,用method就能索引到method的本地指令地址,调用LinkMethod就能将method解析为ArtMethod。

1
2
3
4
5
6
7
8
9
10
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.
}

接下来通过OatMethod::GetCode获得OatMethod结构体中的code_offset_字段(指向的是一个本地机器指令函数,这个本地机器指令函数正是通过翻译该方法的DEX字节码得到的),并且通过调用ArtMethod类的成员函数SetEntryPointFromCompiledCode设置到参数method描述的ArtMethod对象中去。

类方法可以通过本地指令执行,也可以通过解释器执行
NeedsInterpreter检查该类方法是否需要解释器
enter_interpreter?true or false

设置入口点:

需要解释器的非native方法:artInterpreterToInterpreterBridge设置为解释器执行该类方法的入口点
不需要解释器的方法或native方法:artInterpreterToCompiledCodeBridge设置为解释器执行该类方法的入口点(伪入口,间接调用本地指令)
抽象方法 : GetCompiledCodeToInterpreterBridge->Interpreter

调整入口点:

静态类构造方法:Trampoline->wait class initializing->native code

ClassLinker::FixupStaticTrampolines install the Trampoline

需要通过解释器执行的方法:

  1. 没有对应的本地机器指令,即参数code的值等于NULL。
  2. ART虚拟机运行在解释模式中,并且类方法不是JNI方法,并且也不是代理方法

解释器入口都设置为:GetCompiledCodeToInterpreterBridge(to get a unified enterpoint)

注册native方法

ArtMethod类的成员函数UnregisterNative实际上就是将一个JNI方法的初始化入口设置为通过调用函数GetJniDlsymLookupStub获得的一个Stub。这个Stub的作用是,当一个JNI方法被调用时,如果还没有显示地注册有Native函数,那么它就会自动从已加载的SO文件查找是否存在一个对应的Native函数。如果存在的话,就将它注册为JNI方法的Native函数,并且执行它。这就是隐式的JNI方法注册。

UpdateMethodsCode更新方法入口,是否设置监控函数

GetStaticMethodID:

  1. 将参数jni_class的值转换为一个Class指针c,因此就可以得到一个Class对象,并且通过ClassLinker类的成员函数EnsureInitialized确保该Class对象描述的类已经初始化。
  1. Class对象c描述的类在加载的过程中,经过解析已经关联上一系列的成员函数。这些成员函数可以分为两类:Direct和Virtual。Direct类的成员函数包括所有的静态成员函数、私有成员函数和构造函数,而Virtual则包括所有的虚成员函数。因此:
    1. 当参数is_static的值等于true时,那么就表示要查找的是静态成员函数,这时候就在Class对象c描述的类的关联的Direct成员函数列表中查找参数name和sig对应的成员函数。这是通过调用Class类的成员函数FindDirectMethod来实现的。
    1. 当参数is_static的值不等于true时,那么就表示要查找的是虚拟成员函数或者非静态的Direct成员函数,这时候先在Class对象c描述的类的关联的Virtual成员函数列表中查找参数name和sig对应的成员函数。这是通过调用Class类的成员函数FindVirtualMethod来实现的。如果找不到对应的虚拟成员函数,那么再在Class对象c描述的类的关联的Direct成员函数列表中查找参数name和sig对应的成员函数。
  1. 经过前面的查找过程,如果都不能在Class对象c描述的类中找到与参数name和sig对应的成员函数,那么就抛出一个NoSuchMethodError异常。否则的话,就将查找得到的ArtMethod对象封装成一个jmethodID值返回给调用者。