ART学习总结

一、概述

ART是Android平台上的新一代运行时,用来代替dalvik。它主要采用了AOT的方法,在apk安装的时候将dalvikbytecode一次性编译成arm本地指令(但是这种AOT与c语言等还是有本质不同的,还是需要虚拟机的环境支持),这样在运行的时候就无需进行任何解释或编译便可直接执行,节省了运行时间,提高了效率,但是在一定程度上使得安装的时间变长,空间占用变大。(本文所有时序图均基于Android M)

二、OAT文件

简单的说,oat文件是嵌套在一个elf文件的格式中的。在elf文件的动态符号表中有三个重要的符号:oatdata、oatexec、oatlastword,分别表示oat的数据区,oat文件中的native code和结束位置。

guoqifa@guoqifa:~$ readelf -s system@priv-app@Mms@Mms.apk@classes.dex
Symbol table '.dynsym' contains 4 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000001000 0x95b000 OBJECT  GLOBAL DEFAULT    4 oatdata
     2: 000000000095c000 0xb3a4c0 OBJECT  GLOBAL DEFAULT    5 oatexec
     3: 00000000014964bc     4 OBJECT  GLOBAL DEFAULT    5 oatlastword

这些关系结构在图中说明的很清楚,简单理解就是在oatdata中,保存有原来的dex文件内容,在头部还保留了寻址到dex文件内容的偏移地址和指向对应的oat class偏移,oat class中还保存了对应的native code的偏移地址,这样也就间接的完成了dexbytecode和native code的对应关系。具体请参考:Android ART Oat文件格式简析(上)Android ART Oat文件格式简析(下)Android运行时ART加载OAT文件的过程分析. 也可自行分析 art\runtime\Oat_file.cc文件中OatFile::Setup()函数来进一步学习OAT文件。(下图来源老罗博客)

《ART学习总结》 oat文件格式

三、编译

apk在安装时使用dex2oat来编译。大概流程如下图所示,有两种后端编译模式,可选的是所谓快模式(Quick)或优化模式(Optimizing)。如果没有特别指定的话,编译镜像image使用快模式,而编译一般的应用程序则使用优化模式。

《ART学习总结》 dex2oat compile

什么是编译器的后端呢?其实这是LLVM引入的概念。所有的程序先用前端翻译成中间表示层,然后进行优化,最后用后端将优化过的中间表示层代码编译成平台相关的代码。Android虽然没有直接用LLVM编译器(以前也确实用过,但从6.0开始就废弃掉了),但是借鉴了这种编译器的设计结构。值得一提的是,如果使用优化模式,则一定是用PIC模式进行编译。

更具体的compile时序图如下(该时序图只绘出quick编译模式),可自行分析该流程。

《ART学习总结》 Compile Image

四、类加载

ART类的加载通过ClassLinker::DefineClass()函数来完成。该函数会分别调用InsertClass()、LoadClass()、LinkClass()来执行类的加载。

InsertClass()主要是把该类插入class_table_中,方便下次FindClass时直接从LookupClass()中返回结果。LoadClass()用来从指定的DEX文件中加载指定的类,并初始化一个Class对象,Class对象包含了一系列的ArtField对象和ArtMethod对象。LinkClass()用来动态绑定虚函数和接口函数。

类加载完成后,得到的是一个Class对象。这个Class对象关联有一系列的ArtField对象和ArtMethod对象。其中,ArtField对象描述的是成员变量,而ArtMethod对象描述的是成员函数。对于每一个ArtMethod对象,它都有一个解释器入口点和一个本地机器指令入口点。这样,无论一个类方法是通过解释器执行,还是直接以本地机器指令执行,我们都可以以统一的方式来进行调用。

关于类的加载可参考Android运行时ART加载类和方法的过程分析

《ART学习总结》 Load Class

五、方法调用

Zygote孵化应用进程后便会从入口AndroidRuntime.start()进入运行时,进入Java世界。

《ART学习总结》 CallStaticVoidMethod

ART方法调用如上图所示,最终调用到ArtMethod::Invoke()。ArtMethod正是前面类加载章节中提到的ArtMethod对象,“它都有一个解释器入口点和一个本地机器指令入口点”,Invoke()函数正是通过art_quick_invoke_stub()或art_quick_invoke_static_stub()来调用oat文件中的native code的。art_quick_invoke_stub()是平台相关的汇编函数,比如我的机器该函数定义在art/runtime/arch/arm64/quick_entrypoints_arm64.S文件中。更多方法调用的分析请参考Android运行时ART执行类方法的过程分析.

总结:通过查找相关的oat文件,得到所需要的类和方法,并将其对应的native code的位置放入ArtMethod结构,最后通过Invoke成员完成调用。

六、JNI调用

1. Java调用native方法

ArtMethod对象与真实执行的代码链接的过程主要是通过LinkCode()函数执行的。

void ClassLinker::LinkCode(ArtMethod* method, const OatFile::OatClass* oat_class,
                           uint32_t class_def_method_index) {
  ......
  if (method->IsNative()) {
    // Unregistering restores the dlsym lookup stub.
    method->UnregisterNative();
   ......
}
void ArtMethod::UnregisterNative() {
  CHECK(IsNative() && !IsFastNative()) << PrettyMethod(this);
  // restore stub to lookup native pointer via dlsym
  RegisterNative(GetJniDlsymLookupStub(), false);
}

GetJniDlsymLookupStub()函数返回平台相关的art_jni_dlsym_lookup_stub()汇编函数指针,RegisterNative()将该汇编函数指针注册到ArtMethod中,以上是链接过程。

待JNI调用时便会执行到汇编art_jni_dlsym_lookup_stub()函数,该函数会继续调用artFindNativeMethod()函数。

《ART学习总结》 art_jni_dlsym_lookup_stub

extern "C" void* artFindNativeMethod(Thread* self) {
  DCHECK_EQ(self, Thread::Current());
#endif
  Locks::mutator_lock_->AssertNotHeld(self);  // We come here as Native.
  ScopedObjectAccess soa(self);

  ArtMethod* method = self->GetCurrentMethod(nullptr);
  DCHECK(method != nullptr);

  // Lookup symbol address for method, on failure we'll return null with an exception set,
  // otherwise we return the address of the method we found.
  void* native_code = soa.Vm()->FindCodeForNativeMethod(method);
  if (native_code == nullptr) {
    DCHECK(self->IsExceptionPending());
    return nullptr;
  } else {
    // Register so that future calls don't come here
    method->RegisterNative(native_code, false);
    return native_code;
  }
}

artFindNativeMethod()函数就是查找到相应方法的native code,然后再次注册到ArtMethod中,这样以后再执行的时候就直接跳到了native code执行了。

2. native调用Java

native调用Java是通过JNIEnv->FindClass()、JNIEnv->GetStaticMethodID()、JNIEnv->CallVoidMethod()来查找类,得到相应的方法的ID,然后通过此ID去调用。最终如上面第五章方法调用时序图所示,调用的是ArtMethod::Invoke()。即JNI的这些API其实还是做了一遍ART的类加载和初始化及调用的过程。

七、参考文章

1.也来看看Android的ART运行时
2.Android运行时ART简要介绍和学习计划
3.Android ART Oat文件格式简析

    原文作者:guoqifa
    原文地址: https://www.jianshu.com/p/a9bd7254acb4
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞