Android 虚拟机简单介绍——ART、Dalvik、启动流程分析

Android 虚拟机方面的知识,我是经过《深刻理解 Android 内核设计思想》来学习的,内容特别多(只有一章,但有 160 页),感受和 Android 开发有些偏,所以不少内容都没有认真去看,好比 EFL 格式等,这里只是选取了一些感受比较重要的作一个大体的简单的介绍。java

虚拟机基础知识
Java VM
详见《深刻理解 Java 虚拟机》android

LLVM
LLVM 全称是 Low Level Virtual Machine,但和虚拟机没太大关系,官方定义是:The LLVM Project is a colection of modular and resuable compiler and toolchain technologies。即 LLVM 的价值在于可模块化、可重复使用。算法

LLVM 框架以下所示:安全

Frontend:负责分析源代码、检查错误,而后将源码编译成抽象语法树app

Optimizer:经过多种优化手段来提升代码的运行效率,在必定程度上独立于具体的语言和目标平台框架

Backend:也被称为代码生成器,用于将前述的源码转化为目标前台的指令集socket

LLVM 的模块化:模块化

能够看出,若是要让基于 LLVM 框架的编译器支持一种新语言,那么所要作的可能仅仅是实现一个新的 Frontend,而已有的 Optimizer 和 Backend 则能作到重复使用。上述能力获得实现的关键在于 LLVM 的 Intermediate Representation(IR),IR 能在 LLVM 的编译器中(具体在 Optimizer 阶段)以一种相对独立的方式来表述各类源代码,从而很好地剥离了各类不一样语言间的差别,进而实现模块的复用。函数

LLVM 和 Android 的关系能够参考 RednaxelaFX 大神的一个回答:Android 中的 LLVM 主要作什么?工具

简单来讲,就是从 Jellybean MR1 版本开始,Google 将 LLVM 做为 Android 工具链和 Android NDK 的替代编译器,能够用于编译在 Android 应用程序中的 C/C++ 代码。

Android 中的经典垃圾回收算法
Android 中不论是 Dalvik 仍是 Art,它们所使用的垃圾回收算法都是基于 Mark-Sweep 的。

GC 的触发时机有:

GC_FOR_MALLOC。堆内存已满的状况下程序尝试去分配新的内存块

GC_CONCURRENT,堆内存超过特定阈值,触发并行的 GC 事件

GC_HPROF_DUMP_HEAP,开发者主动请求建立 HPROF

GC_EXPLICIT,程序主动调用 gc() 函数,尽可能避免这种用法

Art 和 Dalvik 之争
Dalvik 是 Android 4.4 以前的标准虚拟机,为了性能上的考虑,Dalvik 所作出的努力有:

多个 Class 文件融合进一个 Dex 文件中,以节省内存空间

Dex 文件能够在多个进程之间共享

在应用程序运行以前完成字节码的检验操做,由于检验操做十分耗时

优化字节码

多个进程共享的代码不能随意编辑,这是为了保证安全性

但 Android 从诞生起就背负了“系统庞大,运行慢”的包袱,所以,从 Android 4.4 开始,Art 就以和 Dalvik 暂时共存的形式正式进入了人们的视野,而在 Android Lollipop 中正式取代了 Dalvik 的位置。

Art 相比 Dalvik 在性能上有着显著的优点,主要缘由在于 Dalvik 虚拟机多数状况下还得经过解释器的方式来执行 Dex 数据(JIT 虽然能在必定程度上提升效率,但也仅仅是针对一小部分状况,做用有限);而 Art 虚拟机则采用了 AOT(Ahead Of Time) 技术,从而大幅提升了性能。

Dalvik 中的 JIT只有在程序运行过程当中才会将部分热点代码编译成机器码,这在某种程度上也加剧了 CPU 的负担。而 AOT 则会提早将 Java 代码翻译成针对目标平台的机器码,虽然这也意味着编译时间有所增长,但 Android 系统的构建本来就慢,因此这点牺牲仍是值得的。

Art 虚拟机总体框架
不管是 Dalvik 仍是 Art,或者将来可能出现的新型虚拟机,它们提供的功能将所有封装在一个 so 库中,而且对外须要暴露 JNI_GetDefaultVMInitArgs、JNI_CreateVM 和 JNI_GetCreatedJavaVMs 三个接口,使用者(好比 Zygote)只须要按照统一的接口标准就能够控制和使用全部类型的虚拟机了。

组成 Android 虚拟机的核心自系统包括但不限于 Runtime、ClassLoader System、Execution、Engine System、Heap Manager 和 GC 系统、JIT、JNI 环境等。

和标准的 JVM 同样,类加载器在 Android 虚拟机中也扮演者很重要的做用,能够分为 Boot ClassLoader、System ClassLoader、Dex ClassLoader 等,全部被加载的类和它们的组成元素都将由 ClassLinker 作统一的管理。

除了字节码解释执行的方式,Art 还支持经过 AOT 来直接执行字节码编译而成的机器码。AOT 的编译时机有两个:随 Android ROM 构建时一块儿编译、程序安装时执行编译(针对第三方应用程序)。Art 引入了新的存储格式,即 OAT 文件来存储编译后的机器代码。而 OAT 机器码的加载须要用到 ELF 的基础能力。

另外,因为一股脑地在程序安装阶段将 Dex 转化为 OAT 形成形成了必定的资源浪费,从 Android N 版本开始,Art 又改变了以前的 OAT 策略——程序在安装时再也不统一执行 dex2oat,而改由根据程序的实际运行状况来决定有哪些部分须要被编译成本地代码,即恢复了 Interpreter、JIT、OAT 三足鼎立的局面。一方面,这种新变化大幅加快了程序的安装速度,解决了系统更新时用户须要经历漫长等待的问题;另外一方面,因为程序的首次启动必须经过解释器来运行,Android N 版本必须采用多种手段(新的解释器,将 Verification 前移等)来保证程序的启动速度不受影响。

应用程序除了解释执行外,还会在运行过程当中实时作 JIT 编译——不过它的结果并不会被持久化。另外,虚拟机会记录下应用程序在动态运行过程当中被执行过的函数,并输出到 Profile 文件里。

AOT compile daemon 将在系统同时知足 idle 和充电状态两个条件时才会被唤醒,并按照必定的逻辑来遍历执行应用程序的 AOT 优化。因为参与 AOT 的函数数量一般只占应用程序代码的一小部分,因此总体而言 Android N 版本 AOT 结果所占用的空间大小比旧版本要小不少。

Dex 字节码
Java 类文件和 DEX 文件对比:

ELF 文件格式
Art 虚拟机最大的特色就是经过 dex2oat 将 Dex 预编译为包含了机器指令的 oat 文件,从而显著提高了程序的执行效率。而 oat 文件自己是基于 Linux 的可执行文件格式——ELF 所作的扩展。

ELF 文件至少支持 3 中形态:可重定向文件(Relocatable File)、可执行文件(Executable File)、可共享的对象文件(Shared Object File)。

Relocatable File 的一个具体范例是 .o 文件,它是在编译过程当中产生的中间文件。

Shared Object File 即动态连接库,一般以 “.so” 为后缀名。

静态连接库的特色是会在程序的编译连接阶段就完成函数和变量的地址解析工做,并使之成为可执行程序中不可分割的一部分。

动态连接库不须要在编译时就打包到可执行程序中,而是等到后者在运行阶段在实现动态的加载和重定位。动态连接库在被加载到内存中以后,操做系统须要为它执行动态链接操做。

动态连接库的处理过程以下:

在编译阶段,程序经历了预编译、编译、汇编及连接操做后,最终造成一个 ELF 可执行程序。同时程序所依赖的动态库会被记录到 .dynamic 区段中;加载动态库所需的 Linker 由 .interp 来指示。

程序运行起来后,系统首先会经过 .interp 区段找到链接器的绝对路径,而后将控制权交给它

Linker 负责解析 .dynamic 中的记录,得出程序依赖的全部动态连接库

动态连接库加载完成后,它们所包含的 export 函数在内存中的地址就能够肯定下来了,Linker 经过预设机制(如 GOT)来保证程序中引用到外部函数的地方能够正常工做,即完成 Dynamic Relocation

OAT
与 OAT 相关的文件格式后缀有几种:

.art,这个文件也被称为 image,由 dex2oat 工具生成,它的内部包含了不少 Dex 文件,内部存储的是加载好的class信息以及一些事先建立好的对象,Zygote 在启动过程当中会加载 boot.art

.oat,由 dex2oat 工具生成,boot.oat 内部存储的是代码

.odex,在 Dalvik 中,.odex 表示被优化后的 Dex 文件;Art 中也一样存在 odex 文件,但和 Dalvik 中的状况不一样

应用程序的安装流程:

Android 虚拟机的典型启动流程
Android 虚拟机启动的大体流程图以下:

下面分析具体的代码执行过程,首先看脚本 init.rc 与 zygote 相关的内容:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd
1
2
3
4
5
6
7
app_process 是 zygote 的载体,其 main 函数以下:

int main(int argc, const char* const argv[])
{
    ...
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit",
                startSystemServer ? "start-system-server" : "");
    } else if (className) {
        ...
        runtime.start("com.android.internal.os.RuntimeInit",
                application ? "application" : "tool");
    } else {
        ...
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
runtime 指 AndroidRuntime:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL); // 初始化当前的运行环境
    
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote) != 0) { // 启动虚拟机
        return;
    }
    
    onVmCreated(env); // 虚拟机建立成功,执行回调函数通知调用者
    
    /*
     * 注册 native 函数
     */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }
    
    /*
     * 开始执行目标对象的主函数
     */
    char* slashClassName = toSlashClassName(className != NULL ? className : "");
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        // 执行 main 函数
        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);
#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
在启动虚拟机以前,须要初始化当前的运行环境,具体是由 JniInvocation::Init 实现的:

bool JniInvocation::Init(const char* library) {
    library = GetLibrary(library, buffer); // 获取虚拟机动态连接库的名称

    handle_ = dlopen(library, kDlopenFlags); // 打开虚拟机动态连接库

    ...

    // 分别查找 VM 库中的 3 个重要接口实现
    // 不管是 ART,仍是 Dalvik,或者将来的其它虚拟机都须要实现这 3 个接口,它们是统一的接口标准
    // zygote 经过这 3 个接口控制 Android 的虚拟机
    if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_),
                  "JNI_GetDefaultJavaVMInitArgs")) {
        return false;
    }
    if (!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_),
                  "JNI_CreateJavaVM")) {
        return false;
    }
    if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_),
                  "JNI_GetCreatedJavaVMs")) {
        return false;
    }
    
    return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
如今能够启动和初始化虚拟机了,核心工做是在 startVm 中完成的:

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
{
    ...
    if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
        ALOGE("JNI_CreateJavaVM failed\n");
        return -1;
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
能够看到,它调用了 JniInvocation::Init 中找到的 JNI_CreateJavaVM 接口,该接口用于建立一个虚拟机:

extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
  ...
  
  RuntimeOptions options;
  for (int i = 0; i < args->nOptions; ++i) { // 解析全部的虚拟机选项
    JavaVMOption* option = &args->options[i];
    options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo));
  }
  
  bool ignore_unrecognized = args->ignoreUnrecognized;
  
  if (!Runtime::Create(options, ignore_unrecognized)) { // 建立一个 Runtime 运行环境
    return JNI_ERR;
  }
  
  
  android::InitializeNativeLoader();
  Runtime* runtime = Runtime::Current();
  bool started = runtime->Start(); // 经过 Runtime 来启动其管理的虚拟机
  if (!started) { // 启动失败
    delete Thread::Current()->GetJniEnv();
    delete runtime->GetJavaVM();
    LOG(WARNING) << "CreateJavaVM failed";
    return JNI_ERR;
  }
  
  *p_env = Thread::Current()->GetJniEnv();
  *p_vm = runtime->GetJavaVM(); // 得到已经启动完成的虚拟机示例,并传递给调用者
  
  return JNI_OK;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Runtime::Start 成功启动了一个虚拟机后,会经过 Init 函数来初始化:

bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {

  ...
  
  heap_ = new gc::Heap(...); // 建立堆管理对象
  
  ...
  
  java_vm_ = JavaVMExt::Create(this, runtime_options, &error_msg);  // 建立 Java 虚拟机对象
  
  
  Thread::Startup();
  Thread* self = Thread::Attach("main", false, nullptr, false); // 主线程 attach
  
  if (UNLIKELY(IsAotCompiler())) {
    class_linker_ = new AotClassLinker(intern_table_);
  } else {
    class_linker_ = new ClassLinker(intern_table_);
  }
  
  if (GetHeap()->HasBootImageSpace()) { // 当前 Heap 是否包含 Boot Image(好比 boot.art)
    bool result = class_linker_->InitFromBootImage(&error_msg);
    ...
  } else {
    
    if (runtime_options.Exists(Opt::BootClassPathDexList)) {
      boot_class_path.swap(*runtime_options.GetOrDefault(Opt::BootClassPathDexList));
    } else {
      OpenDexFiles(dex_filenames,
                   dex_locations,
                   runtime_options.GetOrDefault(Opt::Image),
                   &boot_class_path);
    }
    
    instruction_set_ = runtime_options.GetOrDefault(Opt::ImageInstructionSet);
    
    if (!class_linker_->InitWithoutImage(std::move(boot_class_path), &error_msg)) {
      LOG(ERROR) << "Could not initialize without image: " << error_msg;
      return false;
    }
    // TODO: Should we move the following to InitWithoutImage?
    SetInstructionSet(instruction_set_);
    ...
  }
  
  return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
总结:

Android 虚拟机是 init.rc 被解析的时候启动的

init.rc 在解析 zygote 的时候,调用了 AndroidRuntime 的 start 方法来启动 Android 虚拟机

AndroidRuntime 首先经过 JniInvocation::Init 初始化运行环境,找到 JNI_GetDefaultVMInitArgs、JNI_CreateVM 和 JNI_GetCreatedJavaVMs 三个标准接口

接着执行 startVm,这个方法成功执行后,Android 虚拟机就被真正启动了

startVm 内部调用以前找到的标准接口之一 JNI_CreateVM

JNI_CreateVM 内部建立了一个 Runtime 运行环境,并经过 Runtime 启动了虚拟机

虚拟机成功启动后,调用了 Runtime::Init 方法,用于初始化虚拟机,包括:建立 Java 虚拟机、建立 Heap 堆管理对象、加载主线程等

最后,经过 onVmCreated 方法通知虚拟机已经成功建立 ———————————————— 版权声明:本文为CSDN博主「zouzhiheng」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处连接及本声明。 原文连接:https://blog.csdn.net/u011330638/article/details/82830027

相关文章
相关标签/搜索