精华内容
下载资源
问答
  • Android ART虚拟机

    2016-05-04 18:16:59
    Android ART虚拟机 2014-01-18 0 个评论  来源:yangwen123的专栏  收藏 我要投稿 Android 4.4提供了一种与Dalvik截然不同的运行环境ART(Android runtime)支持,ART源于google收购的Flexycore的公司。...
    Android ART虚拟机
    2014-01-18       0  个评论    来源:yangwen123的专栏  
    收藏     我要投稿

    Android 4.4提供了一种与Dalvik截然不同的运行环境ART(Android runtime)支持,ART源于google收购的Flexycore的公司。ART模式与Dalvik模式最大的不同在于,启用ART模式后,系统在安装应用的时候会进行一次预编译,将字节码转换为机器语言存储在本地,这样在运行程序时就不会每次都进行一次编译了,执行效率也大大提升。

    虚拟机切换设置

    Settings> Developer Options> Select Runtime
    <img src="http://www.2cto.com/uploadfile/Collfiles/20140118/2014011809022114.png" alt="" http:="" www.2cto.com="" kf="" ware="" vc="" "="" target="_blank" class="keylink" style="border-width: 0px; padding: 0px; margin: 0px; list-style: none; width: 240px; height: 400px;">vcC0tLS9qNOm08OzzNDyvfizzKOsyqHIpbXEsru99r32ysfTptPDs8zQ8r34s8y0tL2oRGFsdmlr0OnE4rv6tcTKsbzko6xaeWdvdGXNqLn9QW5kcm9pZFJ1dGltZTo6c3RhcnS6r8r9tLS9qERhbHZpa9DpxOK7+qGj1NpBbmRyb2lkz7XNs9bQo6xEYXZpa9DpxOK7+sq1z9bU2mxpYmR2bS5zb9bQo6xBUlTQ6cTiu/rKtc/W1NpsaWJhcnQuc2/W0KOszai5/XBlcnNpc3Quc3lzLmRhbHZpay52bS5saWLPtc2zyvTQ1MC00aHU8dTL0NDWuLao0OnE4rv6oaPO3sLbRGFsdmlru7nKx0FSVKOstrzKtc/WwcvI/bj2z+DNrLXEvdO/2rqvyv2jujxicj4KMS4gSk5JX0dldERlZmF1bHRKYXZhVk1Jbml0QXJncyAtLSC78cih0OnE4rv6tcTErMjPs/XKvLuvss7K/Txicj4KMi4gSk5JX0NyZWF0ZUphdmFWTSAtLSDU2r34s8zW0LS0vajQ6cTiu/rKtcD9PGJyPgozLiBKTklfR2V0Q3JlYXRlZEphdmFWTXMgLS0gu/HIob34s8zW0LS0vai1xNDpxOK7+sq1wP08YnI+Cgo8aW1nIHNyYz0="http://www.2cto.com/uploadfile/Collfiles/20140118/2014011809022218.jpg" alt="\">

    JniInvocation提供统一了接口:
    libnativehelper\JniInvocation.cpp
    jint JniInvocation::JNI_GetDefaultJavaVMInitArgs(void* vmargs) {
      return JNI_GetDefaultJavaVMInitArgs_(vmargs);
    }
    
    jint JniInvocation::JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
      return JNI_CreateJavaVM_(p_vm, p_env, vm_args);
    }
    
    jint JniInvocation::JNI_GetCreatedJavaVMs(JavaVM** vms, jsize size, jsize* vm_count) {
      return JNI_GetCreatedJavaVMs_(vms, size, vm_count);
    }
    该函数间接调用以下函数来实现:
    extern "C" jint JNI_GetDefaultJavaVMInitArgs(void* vm_args) {
      return JniInvocation::GetJniInvocation().JNI_GetDefaultJavaVMInitArgs(vm_args);
    }
    
    extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
      return JniInvocation::GetJniInvocation().JNI_CreateJavaVM(p_vm, p_env, vm_args);
    }
    
    extern "C" jint JNI_GetCreatedJavaVMs(JavaVM** vms, jsize size, jsize* vm_count) {
      return JniInvocation::GetJniInvocation().JNI_GetCreatedJavaVMs(vms, size, vm_count);
    }
    libnativehelper\JniInvocation.h
    static JniInvocation* jni_invocation_;
    void* handle_;//保存so库的首地址
    jint (*JNI_GetDefaultJavaVMInitArgs_)(void*);
    jint (*JNI_CreateJavaVM_)(JavaVM**, JNIEnv**, void*);
    jint (*JNI_GetCreatedJavaVMs_)(JavaVM**, jsize, jsize*);
    因此JniInvocation对外提供的接口函数是由JniInvocation中定义的函数指针来实现。JniInvocation中定义的函数指针是在哪里初始化的呢?我们知道 Android虚拟机是在Zygote进程中通过调用AndroidRuntime::start()函数启动的: frameworks\base\core\jni\AndroidRuntime.cpp
    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;
        }
        onVmCreated(env);
    	...
    }
    在启动虚拟机前通过调用JniInvocation::Init()函数来初始化JniInvocation中声明的函数指针
    bool JniInvocation::Init(const char* library) {
    #ifdef HAVE_ANDROID_OS
      char default_library[PROPERTY_VALUE_MAX];
      property_get("persist.sys.dalvik.vm.lib", default_library, "libdvm.so");
    #else
      const char* default_library = "libdvm.so";
    #endif
      if (library == NULL) {
        library = default_library;
      }
    
      handle_ = dlopen(library, RTLD_NOW);
      if (handle_ == NULL) {
        ALOGE("Failed to dlopen %s: %s", library, dlerror());
        return false;
      }
      //查找JNI_GetDefaultJavaVMInitArgs函数指针
      if (!FindSymbol(reinterpret_cast(&JNI_GetDefaultJavaVMInitArgs_),
                      "JNI_GetDefaultJavaVMInitArgs")) {
        return false;
      }
      //查找JNI_CreateJavaVM函数指针
      if (!FindSymbol(reinterpret_cast(&JNI_CreateJavaVM_),
                      "JNI_CreateJavaVM")) {
        return false;
      }
      //查找JNI_GetCreatedJavaVMs函数指针
      if (!FindSymbol(reinterpret_cast(&JNI_GetCreatedJavaVMs_),
                      "JNI_GetCreatedJavaVMs")) {
        return false;
      }
      return true;
    }
    该函数就是根据persist.sys.dalvik.vm.lib属性值来加载不同的虚拟机动态库,并从动态库中查找JNI_GetDefaultJavaVMInitArgs,JNI_CreateJavaVM,JNI_GetCreatedJavaVMs函数首地址
    bool JniInvocation::FindSymbol(void** pointer, const char* symbol) {
      *pointer = dlsym(handle_, symbol);
      if (*pointer == NULL) {
        ALOGE("Failed to find symbol %s: %s\n", symbol, dlerror());
        dlclose(handle_);
        handle_ = NULL;
        return false;
      }
      return true;
    }

    Dalvik字节码生成过程

    应用安装时采用的代码优化方式不同:
    Dalvik : dex2opt
    ART : dex2oat 
    \

    ART本地码生成过程

    Dalvik dex代码经过dex => optimized dex => JIT cache这个过程,内存中需要同时容纳odex和JIT cache两份代码;换成ART以后,就变成dex => oat,内存里只放oat就可以。


    \

    两个运行环境产生的优化代码路径及文件名都为:/data/dalvik-cache/app/data@app@{package name}.apk@classes.dex,但是ART环境产生的优化代码文件大小明显比Dalvik环境产生大。OAT文件其实就是基于ELF格式的一种私有文件格式。

    dex2oat编译过程

    开发者开发出的应用程序经过编译和打包之后,仍然是一个包含dex字节码的APK文件。既然应用程序包含的仍然是dex字节码,而ART虚拟机需要的是本地机器码,这就必然要有一个翻译的过程。Android系统在应用程序安装时,通过dex2oat工具将应用的dex字节码翻译成本地机器码。


    \

    ART相关源代码:


    OAT文件加载流程

    1、读取oatdata符号地址获取Oat数据 startAddress。

    2、读取oatlastword符号地址获取OAT数据 endAddress。

    3、通过startAddress和endAddress定位Oat数据。

    4、解析Oat数据。构建方法定位所需数据结构。

    然后就可以调用加载OAT文件的代码了。

    展开全文
  • JVM是Java Virtual Machine,而DVM就是Dalvik Virtual Machine,是安卓中使用的虚拟机,所有安卓程序都运行在安卓系统进程里,每个进程对应着一个Dalvik虚拟机实例。他们都提供了对象生命周期管理、堆栈管理、线程...

    转载 https://www.jianshu.com/p/8edac8e09b3e

    1. 什么是JVM?

    JVM本质上就是一个软件,是计算机硬件的一层软件抽象,在这之上才能够运行Java程序,JAVA在编译后会生成类似于汇编语言的JVM字节码,与C语言编译后产生的汇编语言不同的是,C编译成的汇编语言会直接在硬件上跑,但JAVA编译后生成的字节码是在JVM上跑,需要由JVM把字节码翻译成机器指令,才能使JAVA程序跑起来。
    JVM运行在操作系统上,屏蔽了底层实现的差异,从而有了JAVA吹嘘的平台独立性和Write Once Run Anywhere。根据JVM规范实现的具体虚拟机有几十种,主流的JVM包括Hotspot、Jikes RVM等,都是用C/C++和汇编编写的,每个JRE编译的时候针对每个平台编译,因此下载JRE(JVM、Java核心类库和支持文件)的时候是分平台的,JVM的作用是把平台无关的.class里面的字节码翻译成平台相关的机器码,来实现跨平台。

    2.什么是DVM,和JVM有什么不同?

    JVM是Java Virtual Machine,而DVM就是Dalvik Virtual Machine,是安卓中使用的虚拟机,所有安卓程序都运行在安卓系统进程里,每个进程对应着一个Dalvik虚拟机实例。他们都提供了对象生命周期管理、堆栈管理、线程管理、安全和异常管理以及垃圾回收等重要功能,各自拥有一套完整的指令系统,以下简要对比两种虚拟机的不同。

    ①JAVA虚拟机运行的是JAVA字节码,Dalvik虚拟机运行的是Dalvik字节码
    JAVA程序经过编译,生成JAVA字节码保存在class文件中,JVM通过解码class文件中的内容来运行程序。而DVM运行的是Dalvik字节码,所有的Dalvik字节码由JAVA字节码转换而来,并被打包到一个DEX(Dalvik Executable)可执行文件中,DVM通过解释DEX文件来执行这些字节码。

    ②Dalvik可执行文件体积更小
    以下是JVM规范中以C的数据结构表达的class文件结构,class文件被虚拟机加载到内存中后便是这样

    class文件中包含多个不同的方法签名,如果A类文件引用B类文件中的方法,方法签名也会被复制到A类文件中(在虚拟机加载类的连接阶段将会使用该签名链接到B类的对应方法),也就是说,多个不同的类会同时包含相同的方法签名,同样地,大量的字符串常量在多个类文件中也被重复使用,这些冗余信息会直接增加文件的体积,而JVM在把描述类的数据从class文件加载到内存时,需要对数据进行校验、转换解析和初始化,最终才形成可以被虚拟机直接使用的JAVA类型,因为大量的冗余信息,会严重影响虚拟机解析文件的效率。

    为了减小执行文件的体积,安卓使用Dalvik虚拟机,SDK中有个dx工具负责将JAVA字节码转换为Dalvik字节码,dx工具对JAVA类文件重新排列,将所有JAVA类文件中的常量池分解,消除其中的冗余信息,重新组合形成一个常量池,所有的类文件共享同一个常量池,使得相同的字符串、常量在DEX文件中只出现一次,从而减小了文件的体积。

    dx工具的转换过程和DEX文件的结构如下图所示。

    ③JVM基于栈,DVM基于寄存器
    关于栈式虚拟机:
    1.代码必须使用这些指令来移动变量(即push和pop)
    2.代码尺寸小和解码效率会更高些
    3.堆栈虚拟机指令有隐含的操作数。

    关于寄存器式虚拟机:
    1.使用堆栈来分配激活记录器
    2.基于寄存器代码免去了使用push和pop命令的麻烦,减少了每个函数的指令总数。
    3.代码尺寸和解码效率不如基于栈虚拟机,因为它包含操作数,所以指令大于基于堆栈的指令。但是基于寄存器产生更少的代码,所以总的代码数不会增加。
    4.寄存器虚拟机必须从操作指令中解码操作数,需要额外的解码操作。

    基于栈与基于寄存器的指令集,用在解释器里,笼统说有以下对比:

    • 从源码生成代码的难度:基于栈 < 基于寄存器,不过差别不是特别大
    • 表示同样程序逻辑的代码大小(code size):基于栈 < 基于寄存器
    • 表示同样程序逻辑的指令条数(instruction count):基于栈 > 基于寄存器
    • 简易实现中数据移动次数(data movement count):基于栈 > 基于寄存器;不过值得一提的是实现时通过栈顶缓存(top-of-stack caching)可以大幅降低基于栈的解释器的数据移动开销,可以让这部分开销跟基于寄存器的在同等水平。请参考另一个回答:寄存器分配问题? - RednaxelaFX 的回答
    • 采用同等优化程度的解释器速度:基于栈 < 基于寄存器
    • 交由同等优化程度的JIT编译器编译后生成的代码速度:基于栈 === 基于寄存器

    因而,笼统说可以有以下结论:

    • 要追求尽量实现简单:选择基于栈
    • 传输代码的大小尽量小:选择基于栈
    • 纯解释执行的解释器的速度:选择基于寄存器
    • 带有JIT编译器的执行引擎的速度:随便,两者一样;对简易JIT编译器而言基于栈的指令集可能反而更便于生成更快的代码,而对比较优化的JIT编译器而言输入是基于栈还是基于寄存器都无所谓,经过parse之后就变得完全一样了。

    要是拿两个分别实现了基于栈与基于寄存器架构、但没有直接联系的VM来对比,效果或许不会太好。现在恰巧有两者有紧密联系的例子——JVM与Dalvik VM。JVM的字节码主要是零地址形式的,概念上说JVM是基于栈的架构。Google Android平台上的应用程序的主要开发语言是Java,通过其中的Dalvik VM来运行Java程序。为了能正确实现语义,Dalvik VM的许多设计都考虑到与JVM的兼容性;但它却采用了基于寄存器的架构,其字节码主要是二地址/三地址混合形式的,乍一看可能让人纳闷。考虑到Android有明确的目标:面向移动设备,特别是最初要对ARM提供良好的支持。ARM9有16个32位通用寄存器,Dalvik VM的架构也常用16个虚拟寄存器(一样多……没办法把虚拟寄存器全部直接映射到硬件寄存器上了);这样Dalvik VM就不用太顾虑可移植性的问题,优先考虑在ARM9上以高效的方式实现,发挥基于寄存器架构的优势。 Dalvik VM的主要设计者Dan Bornstein在Google I/O 2008上做过一个关于Dalvik内部实现的演讲;同一演讲也在Google Developer Day 2008 China和Japan等会议上重复过。这个演讲中Dan特别提到了Dalvik VM与JVM在字节码设计上的区别,指出Dalvik VM的字节码可以用更少指令条数、更少内存访问次数来完成操作。

    眼见为实。要自己动手感受一下该例子。

    创建Demo.java文件,内容为:

    public class Demo {
        public static void foo() {
            int a = 1;
            int b = 2;
            int c = (a + b) * 5;
        }
    }
    

    通过javac编译,得到Demo.class。通过javap可以看到foo()方法的字节码是:

    0:  iconst_1
    1:  istore_0
    2:  iconst_2
    3:  istore_1
    4:  iload_0
    5:  iload_1
    6:  iadd
    7:  iconst_5
    8:  imul
    9:  istore_2
    10: return
    

    接着用Android SDK里platforms\android-1.6\tools目录中的dx工具将Demo.class转换为dex格式。转换时可以直接以文本形式dump出dex文件的内容。使用下面的命令:

    ** Command prompt 代码 **

    dx --dex --verbose --dump-to=Demo.dex.txt --dump-method=Demo.foo --verbose-dump Demo.class
    

    可以看到foo()方法的字节码是:
    ** Dalvik bytecode代码 **

    0000: const/4       v0, #int 1 // #1
    0001: const/4       v1, #int 2 // #2
    0002: add-int/2addr v0, v1
    0003: mul-int/lit8  v0, v0, #int 5 // #05
    0005: return-void
    

    让我们看看两个版本在概念上是如何工作的。

    ** JVM: **

     

    (图中数字均以十六进制表示。其中字节码的一列表示的是字节码指令的实际数值,后面跟着的助记符则是其对应的文字形式。标记为红色的值是相对上一条指令的执行状态有所更新的值。下同)

    说明:
    Java字节码以1字节为单元。上面代码中有11条指令,每条都只占1单元,共11单元==11字节。 程序计数器是用于记录程序当前执行的位置用的。对Java程序来说,每个线程都有自己的PC。PC以字节为单位记录当前运行位置里方法开头的偏移量。
    每个线程都有一个Java栈,用于记录Java方法调用的“活动记录”(activation record)。Java栈以帧(frame)为单位线程的运行状态,每调用一个方法就会分配一个新的栈帧压入Java栈上,每从一个方法返回则弹出并撤销相应的栈帧。
    每个栈帧包括局部变量区、求值栈(JVM规范中将其称为“操作数栈”)和其它一些信息。局部变量区用于存储方法的参数与局部变量,其中参数按源码中从左到右顺序保存在局部变量区开头的几个slot。求值栈用于保存求值的中间结果和调用别的方法的参数等。两者都以字长(32位的字)为单位,每个slot可以保存byte、short、char、int、float、reference和returnAddress等长度小于或等于32位的类型的数据;相邻两项可用于保存long和double类型的数据。每个方法所需要的局部变量区与求值栈大小都能够在编译时确定,并且记录在.class文件里。
    在上面的例子中,Demo.foo()方法所需要的局部变量区大小为3个slot,需要的求值栈大小为2个slot。Java源码的a、b、c分别被分配到局部变量区的slot 0、slot 1和slot 2。可以观察到Java字节码是如何指示JVM将数据压入或弹出栈,以及数据是如何在栈与局部变量区之前流动的;可以看到数据移动的次数特别多。动画里可能不太明显,iadd和imul指令都是要从求值栈弹出两个值运算,再把结果压回到栈上的;光这样一条指令就有3次概念上的数据移动了。
    Java的局部变量区并不需要把某个局部变量固定分配在某个slot里;不仅如此,在一个方法内某个slot甚至可能保存不同类型的数据。如何分配slot是编译器的自由。从类型安全的角度看,只要对某个slot的一次load的类型与最近一次对它的store的类型匹配,JVM的字节码校验器就不会抱怨。以后再找时间写写这方面。

    ** Dalvik VM: **

    说明:
    Dalvik字节码以16位为单元(或许叫“双字节码”更准确 =_=|||)。上面代码中有5条指令,其中mul-int/lit8指令占2单元,其余每条都只占1单元,共6单元==12字节。
    与JVM相似,在Dalvik VM中每个线程都有自己的PC和调用栈,方法调用的活动记录以帧为单位保存在调用栈上。PC记录的是以16位为单位的偏移量而不是以字节为单位的。
    与JVM不同的是,Dalvik VM的栈帧中没有局部变量区与求值栈,取而代之的是一组虚拟寄存器。每个方法被调用时都会得到自己的一组虚拟寄存器。常用v0-v15这16个,也有少数指令可以访问v0-v255范围内的256个虚拟寄存器。与JVM相同的是,每个方法所需要的虚拟寄存器个数都能够在编译时确定,并且记录在.dex文件里;每个寄存器都是字长(32位),相邻的一对寄存器可用于保存64位数据。方法的参数按源码中从左到右的顺序保存在末尾的几个虚拟寄存器里。
    与JVM版相比,可以发现Dalvik版程序的指令数明显减少了,数据移动次数也明显减少了,用于保存临时结果的存储单元也减少了。

    你可能会抱怨:上面两个版本的代码明明不对应:JVM版到return前完好持有a、b、c三个变量的值;而Dalvik版到return-void前只持有b与c的值(分别位于v0与v1),a的值被刷掉了。

    但注意到a与b的特征:它们都只在声明时接受过一次赋值,赋值的源是常量。这样就可以对它们应用常量传播,将

    int c = (a + b) * 5;
    

    替换为

    int c = (1 + 2) * 5;
    

    然后可以再对c的初始化表达式应用常量折叠,进一步替换为:

    int c = 15;
    

    把变量的每次状态更新(包括初始赋值在内)称为变量的一次“定义”(definition),把每次访问变量(从变量读取值)称为变量的一次“使用”(use),则可以把代码整理为“使用-定义链”(简称UD链,use-define chain)。显然,一个变量的某次定义要被使用过才有意义。上面的例子经过常量传播与折叠后,我们可以分析得知变量a、b、c都只被定义而没有被使用。于是它们的定义就成为了无用代码(dead code),可以安全的被消除。 上面一段的分析用一句话描述就是:由于foo()里没有产生外部可见的副作用,所以foo()的整个方法体都可以被优化为空。经过dx工具处理后,Dalvik版程序相对JVM版确实是稍微优化了一些,不过没有影响程序的语义,程序的正确性是没问题的。这是其一。

    其二是Dalvik版代码只要多分配一个虚拟寄存器就能在return-void前同时持有a、b、c三个变量的值,指令几乎没有变化:

    0000: const/4      v0, #int 1 // #1
    0001: const/4      v1, #int 2 // #2
    0002: add-int      v2, v0, v1
    0004: mul-int/lit8 v2, v2, #int 5 // #05
    0006: return-void
    

    这样比原先的版本多使用了一个虚拟寄存器,指令方面也多用了一个单元(add-int指令占2单元);但指令的条数没变,仍然是5条,数据移动的次数也没变。

    题外话1:Dalvik VM是基于寄存器的,x86也是基于寄存器的,但两者的“寄存器”却相当不同:前者的寄存器是每个方法被调用时都有自己一组私有的,后者的寄存器则是全局的。也就是说,概念上Dalvik VM字节码中不用担心保护寄存器的问题,某个方法在调用了别的方法返回过来后自己的寄存器的值肯定跟调用前一样。而x86程序在调用函数时要考虑清楚calling convention,调用方在调用前要不要保护某些寄存器的当前状态,还是说被调用方会处理好这些问题,麻烦事不少。Dalvik VM这种虚拟寄存器让人想起一些实际处理器的“寄存器窗口”,例如SPARC的Register Windows也是保证每个函数都觉得自己有“私有的一组寄存器”,减轻了在代码里处理寄存器保护的麻烦——扔给硬件和操作系统解决了。IA-64也有寄存器窗口的概念。

    题外话2:Dalvik的.dex文件在未压缩状态下的体积通常比同等内容的.jar文件在deflate压缩后还要小。但光从字节码看,Java字节码几乎总是比Dalvik的小,那.dex文件的体积是从哪里来减出来的呢?这主要得益与.dex文件对常量池的压缩,一个.dex文件中所有类都共享常量池,使得相同的字符串、相同的数字常量等都只出现一次,自然能大大减小体积。相比之下,.jar文件中每个类都持有自己的常量池,诸如"Ljava/lang/Object;"这种常见的字符串会被重复多次。Sun自己也有进一步压缩JAR的工具,Pack200,对应的标准是JSR 200。它的主要应用场景是作为JAR的网络传输格式,以更高的压缩比来减少文件传输时间。在官方文档提到了Pack200所用到的压缩技巧。

    3.什么是ART虚拟机,和JVM/DVM有什么不同?

    首先了解JIT(Just In Time,即时编译技术)和AOT(Ahead Of Time,预编译技术)两种编译模式。

    JIT以JVM为例,javac把程序源码编译成JAVA字节码,JVM通过逐条解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译,执行速度必然比C/C++编译后的可执行二进制字节码程序慢,为了提高执行速度,就引入了JIT技术,JIT会在运行时分析应用程序的代码,识别哪些方法可以归类为热方法,这些方法会被JIT编译器编译成对应的汇编代码,然后存储到代码缓存中,以后调用这些方法时就不用解释执行了,可以直接使用代码缓存中已编译好的汇编代码。这能显著提升应用程序的执行效率。(安卓Dalvik虚拟机在2.2中增加了JIT)

    相对的AOT就是指C/C++这类语言,编译器在编译时直接将程序源码编译成目标机器码,运行时直接运行机器码。

    Dalvik虚拟机执行的是dex字节码,ART虚拟机执行的是本地机器码

    Dalvik执行的是dex字节码,依靠JIT编译器去解释执行,运行时动态地将执行频率很高的dex字节码翻译成本地机器码,然后在执行,但是将dex字节码翻译成本地机器码是发生在应用程序的运行过程中,并且应用程序每一次重新运行的时候,都要重新做这个翻译工作,因此,及时采用了JIT,Dalvik虚拟机的总体性能还是不能与直接执行本地机器码的ART虚拟机相比。

    安卓运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者重新将自己的应用直接编译成目标机器码,也就是说,应用程序仍然是一个包含dex字节码的apk文件。所以在安装应用的时候,dex中的字节码将被编译成本地机器码,之后每次打开应用,执行的都是本地机器码。移除了运行时的解释执行,效率更高,启动更快。(安卓在4.4中发布了ART运行时)

    ART优点:

    ①系统性能显著提升
    ②应用启动更快、运行更快、体验更流畅、触感反馈更及时
    ③续航能力提升
    ④支持更低的硬件

    ART缺点

    ①更大的存储空间占用,可能增加10%-20%
    ②更长的应用安装时间

    总的来说ART就是“空间换时间”

    android 不同版本在虚拟机上的选择

    android4.4以上系统中采用默认采用ART模式,因此4.4.2 和 4.4.4 中都支持ART模式。存在Dalvik和ART可选

    展开全文
  • 本文博客地址: ... ...在前面的博客中已经提到了,但是自我感觉分析和理解的还不够透彻,后面又对DexHunter脱壳工具的源码再阅读了几遍,总算是把DexHunter脱壳工具在Dalvik虚拟机模式下和ART虚拟

    本文博客地址: http://blog.csdn.net/qq1084283172/article/details/78494620


    DexHunter脱壳工具在Dalvik虚拟机模式下的脱壳原理分析,在前面的博客中已经提到了,但是自我感觉分析和理解的还不够透彻,后面又对DexHunter脱壳工具的源码再阅读了几遍,总算是把DexHunter脱壳工具在Dalvik虚拟机模式下和ART虚拟机模式下的脱壳原理理解清楚了。关于DexHunter脱壳工具在Dalvik虚拟机模式下的脱壳原理分析,已经准备了一篇博客想详细和深入的讲一讲,一直搁置了没有完成;本来还计划把DexHunter脱壳工具在ART虚拟机模式下的脱壳原理也详细说一说,看来是不可能了。有关Android加固脱壳和Hook的好几个源码工程都看的差不多了,因为各种事情和状态不好,懒得动笔了。


    在理解DexHunter脱壳工具在ART虚拟机模式下的脱壳原理之前,建议先阅读一下老罗的博客了解一下Android的ART虚拟机模式下的类加载和dex文件的优化过程,《Android运行时ART加载OAT文件的过程分析》这篇博客需要好好读一读。DexHunter在ART虚拟机模式下的脱壳编译配置主要是针对源码文件 /art/runtime/class_linker.cc  进行修改,以Android 4.4.4 r1的源码文件为示例 http://androidxref.com/4.4.4_r1/xref/art/runtime/class_linker.cc,然后对Android系统源码进行重新编译生成Android系统的镜像文件,刷到手机设备或者Android模拟器上使用,用来进行DexHunter在ART虚拟机模式下的脱壳操作。


    //-----------------------added begin-----------------------//
    
    /**
     *
     // OAT文件里面的oatdata段的开始储存着一个OAT头OatHeader
       const OatHeader& OatFile::GetOatHeader() const {
    	  return *reinterpret_cast<const OatHeader*>(Begin());
    	}
    
    	// OAT文件里面的oatdata段开始地址
    	const byte* OatFile::Begin() const {
    	  CHECK(begin_ != NULL);
    	  return begin_;
    	}
    
    	//  OAT文件里面的oatexec段的结束地址
    	const byte* OatFile::End() const {
    	  CHECK(end_ != NULL);
    	  return end_;
    	}
    
     // OAT文件里面的oatdata段的开始储存着一个OAT头,这个OAT头通过类OatHeader描述
     class PACKED(4) OatHeader {
    	 public:
    	   ......
    	 private:
    	  uint8_t magic_[4];	// 标识OAT文件的魔法数,等于‘oat\n’
    	  uint8_t version_[4];	// OAT文件版本号,目前的值等于‘007、0’。
    	  uint32_t adler32_checksum_;	// OAT头部检验和
    
    	  // 本地机指令集,有四种取值,分别为  kArm(1)、kThumb2(2)、kX86(3)和kMips(4)。
    	  InstructionSet instruction_set_;
    	  // OAT文件包含的DEX文件个数。
    	  uint32_t dex_file_count_;
    	  // oatexec段开始位置与oatdata段开始位置的相对偏移值。
    	  uint32_t executable_offset_;
    
    	  // 在由打包在应用程序里面的classes.dex生成的OAT文件的oatdata段头部中,下述七个成员变量的值均等于0。
    	  uint32_t interpreter_to_interpreter_bridge_offset_;
    	  uint32_t interpreter_to_compiled_code_bridge_offset_;
    	  uint32_t jni_dlsym_lookup_offset_;
    	  uint32_t portable_resolution_trampoline_offset_;
    	  uint32_t portable_to_interpreter_bridge_offset_;
    	  uint32_t quick_resolution_trampoline_offset_;
    	  uint32_t quick_to_interpreter_bridge_offset_;
    
    	  // 用来创建Image空间的OAT文件的检验和。
    	  uint32_t image_file_location_oat_checksum_;
    	  // 用来创建Image空间的OAT文件的oatdata段在内存的位置。
    	  uint32_t image_file_location_oat_data_begin_;
    
    	  // 用来创建Image空间的文件的路径的大小。
    	  uint32_t image_file_location_size_;
    	  // 用来创建Image空间的文件的路径的在内存中的地址。
    	  uint8_t image_file_location_data_[0];  // note variable width data at end
    
    	  ......
     };
     // 参考:http://blog.csdn.net/Luoshengyang/article/details/39307813
     **/
    
    
    #include <asm/siginfo.h>
    #define LOGI
    
    static char dexname[100]={0};
    
    static char dumppath[100]={0};
    
    static bool readable=true;
    
    static pthread_mutex_t read_mutex;
    
    static bool flag=true;
    
    static pthread_mutex_t mutex;
    
    static bool timer_flag=true;
    
    static timer_t timerId;
    
    
    // 自定义的线程回调的传入参数
    struct arg{
        const DexFile* dex_file;
        mirror::ClassLoader* class_loader;
        ClassLinker* cl;
    }param;
    
    // 类成员信息
    struct DexField {
        uint32_t delta_fieldIdx;    
        uint32_t accessFlags;
    };
    
    // 类方法信息
    struct DexMethod {
        uint32_t delta_methodIdx;
        uint32_t accessFlags;
        uint32_t codeOff; 
    };
    
    struct DexClassDataHeader {
        uint32_t staticFieldsSize;
        uint32_t instanceFieldsSize;
        uint32_t directMethodsSize;
        uint32_t virtualMethodsSize;
    };
    
    // 整个类的方法和成员信息
    struct DexClassData {
        DexClassDataHeader header;
        DexField*          staticFields;
        DexField*          instanceFields;
        DexMethod*         directMethods;
        DexMethod*         virtualMethods;
    };
    
    // 等待dump任务执行的等待定时器
    void timer_thread(::sigval_t)
    {
        timer_flag=false;
        timer_delete(timerId);
        #ifdef LOGI
        LOG(INFO)<<"GOT IT time up";
        #endif
    }
    
    
    // 读取脱壳配置文件
    void* ReadThread(void *arg){
    	
        FILE *fp = NULL;
        while (dexname[0]==0||dumppath[0]==0) {
    		
    		// 打开apk脱壳配置文件/data/dexname
    		// 一些加固会检测这个配置文件/data/dexname
            fp=fopen("/data/dexname", "r");
            if (fp==NULL) {
    			
                sleep(1);
                continue;
            }
    		
    		// 获取被加固的dex文件内存加载的文件路径
            fgets(dexname, 99, fp);
            dexname[strlen(dexname)-1]=0;
    		
    		// 保存被加固dex文件的dump路径
            fgets(dumppath, 99, fp);
            dumppath[strlen(dumppath)-1]=0;
            
            fclose(fp);
            fp=NULL;
        }
    
        struct ::sigevent sev;
        sev.sigev_notify=SIGEV_THREAD;
        sev.sigev_value.sival_ptr=&timerId;
        // 设置定时器的回调函数
        sev.sigev_notify_function=timer_thread;
        sev.sigev_notify_attributes = NULL;
        // 创建定时器
        timer_create(CLOCK_REALTIME,&sev,&timerId);
    
        struct itimerspec ts;
        ts.it_value.tv_sec=5;
        ts.it_value.tv_nsec=0;
        ts.it_interval.tv_sec=0;
        ts.it_interval.tv_nsec=0;
        // 设置定时器的工作时间频率
        timer_settime(timerId,0,&ts,NULL);
    
        return NULL;
    }
    
    // leb128数据的写
    void writeUnsignedLeb128(uint8_t ** ptr, uint32_t data)
    {
        while (true) {
            uint8_t out = data & 0x7f;
            if (out != data) {
                *(*ptr)++ = out | 0x80;
                data >>= 7;
            } else {
                *(*ptr)++ = out;
                break;
            }
        }
    }
    
    // leb128数据的字节长度
    int unsignedLeb128Size(uint32_t data)
    {
        int count = 0;
        do {
            data >>= 7;
            count++;
        } while (data != 0);
    
        return count;
    }
    
    // 获取DexClassDataHeader结构体的各个成员的数据信息
    void dexReadClassDataHeader(const uint8_t** pData,
            DexClassDataHeader *pHeader) {
        pHeader->staticFieldsSize = DecodeUnsignedLeb128(pData);
        pHeader->instanceFieldsSize = DecodeUnsignedLeb128(pData);
        pHeader->directMethodsSize = DecodeUnsignedLeb128(pData);
        pHeader->virtualMethodsSize = DecodeUnsignedLeb128(pData);
    }
    
    // 获取DexClassData结构体DexFiled的数据信息
    void dexReadClassDataField(const uint8_t** pData, DexField* pField) {
        pField->delta_fieldIdx = DecodeUnsignedLeb128(pData);
        pField->accessFlags = DecodeUnsignedLeb128(pData);
    }
    
    // 获取DexClassData结构体DexMethod的数据信息
    void dexReadClassDataMethod(const uint8_t** pData, DexMethod* pMethod) {
        pMethod->delta_methodIdx = DecodeUnsignedLeb128(pData);
        pMethod->accessFlags = DecodeUnsignedLeb128(pData);
        pMethod->codeOff = DecodeUnsignedLeb128(pData);
    }
    
    // 从内存dex文件中读取指定DexFile::ClassDef对应的DexClassData即类成员和类方法描述数据到申请内存空间中
    DexClassData* dexReadClassData(const uint8_t** pData) {
    
        DexClassDataHeader header;
    
        if (*pData == NULL) {
            return NULL;
        }
    
    	// 获取DexClassDataHeader结构体的各个成员的数据信息()
        dexReadClassDataHeader(pData,&header);
    	
        // 计算DexClassData结构体及其所有DexFiled和所有DexMethod占用的内存空间
        size_t resultSize = sizeof(DexClassData) + 
    						(header.staticFieldsSize * sizeof(DexField)) + 
    						(header.instanceFieldsSize * sizeof(DexField)) + 
    						(header.directMethodsSize * sizeof(DexMethod)) + 
    						(header.virtualMethodsSize * sizeof(DexMethod));
    	
        // 申请内存空间
        DexClassData* result = (DexClassData*) malloc(resultSize);
        if (result == NULL) {
            return NULL;
        }
    
    	// 更新指针的位置,用于存放类静态成员变量、类实例成员变量、类直接方法以及类虚拟方法
        uint8_t* ptr = ((uint8_t*) result) + sizeof(DexClassData);
        result->header = header;
    
        if (header.staticFieldsSize != 0) {
    		// 设置存放静态类成员变量的内存起始地址
            result->staticFields = (DexField*) ptr;
    		// 存放所有类静态成员变量需要的内存大小
            ptr += header.staticFieldsSize * sizeof(DexField);
        } else {
            result->staticFields = NULL;
        }
    
        if (header.instanceFieldsSize != 0) {
    		// 设置存放实例类成员变量的内存起始地址
            result->instanceFields = (DexField*) ptr;
            ptr += header.instanceFieldsSize * sizeof(DexField);
        } else {
            result->instanceFields = NULL;
        }
    
        if (header.directMethodsSize != 0) {
    		// 设置存放直接类成员方法的内存起始地址
            result->directMethods = (DexMethod*) ptr;
            ptr += header.directMethodsSize * sizeof(DexMethod);
        } else {
            result->directMethods = NULL;
        }
    
        if (header.virtualMethodsSize != 0) {
    		// 设置存放虚拟类成员方法的内存起始地址
            result->virtualMethods = (DexMethod*) ptr;
        } else {
            result->virtualMethods = NULL;
        }
    
    	// 获取DexClassData中类静态变量和类实例变量以及类直接方法和类虚方法的数据
    	// 存放到指定的申请内存空间中
        for (uint32_t i = 0; i < header.staticFieldsSize; i++) {
            dexReadClassDataField(pData, &result->staticFields[i]);
        }
        for (uint32_t i = 0; i < header.instanceFieldsSize; i++) {
            dexReadClassDataField(pData, &result->instanceFields[i]);
        }
        for (uint32_t i = 0; i < header.directMethodsSize; i++) {
            dexReadClassDataMethod(pData, &result->directMethods[i]);
        }
        for (uint32_t i = 0; i < header.virtualMethodsSize; i++) {
            dexReadClassDataMethod(pData, &result->virtualMethods[i]);
        }
    
        return result;
    }
    
    // 将整个DexClassData所表示的类数据leb128编码写入到申请的内存空间中
    uint8_t* dexEncodeClassData(DexClassData *pData, int& len)
    {
        len=0;
    	
    	// 获取整个DexClassData所表示类数据的内存占用大小
        len+=unsignedLeb128Size(pData->header.staticFieldsSize);
        len+=unsignedLeb128Size(pData->header.instanceFieldsSize);
        len+=unsignedLeb128Size(pData->header.directMethodsSize);
        len+=unsignedLeb128Size(pData->header.virtualMethodsSize);
        if (pData->staticFields) {
            for (uint32_t i = 0; i < pData->header.staticFieldsSize; i++) {
                len+=unsignedLeb128Size(pData->staticFields[i].delta_fieldIdx);
                len+=unsignedLeb128Size(pData->staticFields[i].accessFlags);
            }
        }
        if (pData->instanceFields) {
            for (uint32_t i = 0; i < pData->header.instanceFieldsSize; i++) {
                len+=unsignedLeb128Size(pData->instanceFields[i].delta_fieldIdx);
                len+=unsignedLeb128Size(pData->instanceFields[i].accessFlags);
            }
        }
        if (pData->directMethods) {
            for (uint32_t i=0; i<pData->header.directMethodsSize; i++) {
                len+=unsignedLeb128Size(pData->directMethods[i].delta_methodIdx);
                len+=unsignedLeb128Size(pData->directMethods[i].accessFlags);
                len+=unsignedLeb128Size(pData->directMethods[i].codeOff);
            }
        }
        if (pData->virtualMethods) {
            for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++) {
                len+=unsignedLeb128Size(pData->virtualMethods[i].delta_methodIdx);
                len+=unsignedLeb128Size(pData->virtualMethods[i].accessFlags);
                len+=unsignedLeb128Size(pData->virtualMethods[i].codeOff);
            }
        }
    
    	// 根据整个DexClassData所示类数据的大小申请内存空间
        uint8_t * store = (uint8_t *) malloc(len);
        if (!store) {
    		
    		// 申请内存失败的情况
            return NULL;
        }
    
        uint8_t * result=store;
    	
    	// 将整个DexClassData所表示的类数据写入到申请的内存空间中
        writeUnsignedLeb128(&store,pData->header.staticFieldsSize);
        writeUnsignedLeb128(&store,pData->header.instanceFieldsSize);
        writeUnsignedLeb128(&store,pData->header.directMethodsSize);
        writeUnsignedLeb128(&store,pData->header.virtualMethodsSize);
    	// 类静态成员变量
        if (pData->staticFields) {
            for (uint32_t i = 0; i < pData->header.staticFieldsSize; i++) {
                writeUnsignedLeb128(&store,pData->staticFields[i].delta_fieldIdx);
                writeUnsignedLeb128(&store,pData->staticFields[i].accessFlags);
            }
        }
    	// 类实例变量
        if (pData->instanceFields) {
            for (uint32_t i = 0; i < pData->header.instanceFieldsSize; i++) {
                writeUnsignedLeb128(&store,pData->instanceFields[i].delta_fieldIdx);
                writeUnsignedLeb128(&store,pData->instanceFields[i].accessFlags);
            }
        }
    	// 类直接方法
        if (pData->directMethods) {
            for (uint32_t i=0; i<pData->header.directMethodsSize; i++) {
                writeUnsignedLeb128(&store,pData->directMethods[i].delta_methodIdx);
                writeUnsignedLeb128(&store,pData->directMethods[i].accessFlags);
                writeUnsignedLeb128(&store,pData->directMethods[i].codeOff);
            }
        }
    	// 类虚拟方法
        if (pData->virtualMethods) {
            for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++) {
                writeUnsignedLeb128(&store,pData->virtualMethods[i].delta_methodIdx);
                writeUnsignedLeb128(&store,pData->virtualMethods[i].accessFlags);
                writeUnsignedLeb128(&store,pData->virtualMethods[i].codeOff);
            }
        }
    
        free(pData);
        return result;
    }
    
    // 根据try...catch{}的处理语句计算DexFile::CodeItem结构体的结束地址
    uint8_t* codeitem_end(const uint8_t **pData)
    {
        uint32_t num_of_list = DecodeUnsignedLeb128(pData);
        for (;num_of_list>0;num_of_list--) {
            int32_t num_of_handlers=DecodeSignedLeb128(pData);
            int num=num_of_handlers;
            if (num_of_handlers<=0) {
                num=-num_of_handlers;
            }
            for (; num > 0; num--) {
                DecodeUnsignedLeb128(pData);
                DecodeUnsignedLeb128(pData);
            }
            if (num_of_handlers<=0) {
                DecodeUnsignedLeb128(pData);
            }
        }
        return (uint8_t*)(*pData);
    }
    
    
    // 内存dump加固的dex文件的类数据信息
    void* DumpClass(void *parament)
    {
    	
      while (timer_flag) {
          sleep(5);
      }
      
      // 获取当前art虚拟机的运行时Runtime
      Runtime* runtime = Runtime::Current();
      // 附加到art虚拟机线程
      runtime->AttachCurrentThread("ClassDumper", false, NULL,false);
      // 获取当前art虚拟机的线程描述结构体Thread
      Thread *self=Thread::Current();
    
      #ifdef LOGI
      LOG(INFO)<<"GOT IT DumpingClass";
      #endif
    
      #ifdef LOGI
      // 获取当前时间
      uint64_t time = MilliTime();
      LOG(INFO)<<"GOT IT begin "<<time<<" ms";
      #endif
    
      // 当前Dex文件的内存镜像OAT描述相关的结构体
      const DexFile &dex_file=(*((struct arg*)parament)->dex_file);
      mirror::ClassLoader *class_loader=((struct arg*)parament)->class_loader;
      ClassLinker *cl = ((struct arg*)parament)->cl;
    
      char *path = new char[100];
      strcpy(path, dumppath);
      // 拼接字符串得到文件路径 xxxx/classdef
      strcat(path, "classdef");
      // 打开文件xxxx/classdef
      FILE *fp = fopen(path, "wb+");
    
      strcpy(path, dumppath);
      // 拼接字符串得到文件路径xxxx/extra
      strcat(path, "extra");
      // 打开文件xxxx/extra
      FILE *fp1 = fopen(path,"wb+");
    
      uint32_t mask=0x3ffff;
      char padding=0;
      const char* header="Landroid";
      bool pass=false;
    
      // 锁
      Locks::mutator_lock_->SharedLock(self);
      // 获取当前dex文件的ClassDef的数量
      size_t num_class_defs = dex_file.NumClassDefs();
      // 获取整个OAT文件的文件大小
      uint32_t total_pointer = dex_file.Size();
      uint32_t rec = total_pointer;
    
      // 获取OAT文件内存4字节对齐后的文件大小
      while (total_pointer&3) {
          total_pointer++;
      }
      // 获取OAT文件4字节对齐需要填充'0'的数量
      int inc = total_pointer - rec;
    
      // 获取OAT文件里dex文件的ClassDef结构体表Table的结束地址
      uint32_t start = dex_file.class_defs_off_ + sizeof(DexFile::ClassDef)*num_class_defs;
      // 获取OAT文件的文件大小即文件偏移
      uint32_t end = dex_file.Size();
    
      // 遍历获取OAT文件里的dex文件的ClassDef并加载
      for (size_t i = 0; i < num_class_defs; i++)
      {
    	  // 获取OAT文件里的dex文件的第i个ClassDef结构体
          const DexFile::ClassDef &class_def = dex_file.GetClassDef(i);
          // 获取ClassDef结构体描述的类的类签名字符串,例如:Landroid/xxx/yyy;
          const char* descriptor = dex_file.GetClassDescriptor(class_def);
    
          // 描述dump的类是否需要额外的类数据信息
          bool need_extra = false;
    
          // ART下dex文件的类内存加载后的描述结构体为mirror::Class
          mirror::Class* klass=NULL;
          const byte * data=NULL;
          DexClassData* pData = NULL;
    
          #ifdef LOGI
          LOG(INFO) << "GOT IT " << descriptor;
          #endif
    
          // 过滤掉Android系统("Landroid")的类和没有类数据的无效类(这两中情况不处理)
          if(!strncmp(header, descriptor, 8)||!class_def.class_data_off_)
          {
              pass = true;
              // 此种情况,need_extra = false
              goto classdef;
          }
    
          // 查找指定类签名描述的目标类
          /** mirror::Class* **/klass = cl->FindClass(descriptor, class_loader);
          // 查找的目标类没有找到的情况
          if (!klass) {
    
              #ifdef LOGI
              LOG(INFO)<<"GOT IT class Find Fail";
              #endif
    
              // 异常处理
              self->ClearException();
              continue;
          }
    
          // 成功查找到指定类签名描述的目标类
          if(klass){
    
              if(cl->EnsureInitialized(klass, true, true)){
                  #ifdef LOGI
                  LOG(INFO)<<"GOT IT "<<descriptor<<" Initialized";
                  #endif
              }else{
                  self->ClearException();
              }
          }
    
          // OAT文件里dex文件的Class_Data数据存放在OAT文件的结尾或者Class_Def结构体表开始地址之前位置
          if(class_def.class_data_off_<start || class_def.class_data_off_>end)
          {
              #ifdef LOGI
              LOG(INFO)<<"GOT IT class data off exceeding "<<descriptor;
              #endif
              // 需要额外的处理Class_Def
              need_extra=true;
          }
    
          // 获取Class_Def描述的类的ClassData数据class_data_item
          data = dex_file.GetClassData(class_def);
          // 读取OAT文件里dex文件的类描述DexClassData描述的类成员和类方法的数据到申请的内存空间中
          pData = dexReadClassData(&data);
          if (!pData) {
    
        	  // 失败情况
              continue;
          }
    
          // 针对类直接方法的处理
          if (pData->directMethods) {
    
        	  // 遍历art下类的直接方法
              for (uint32_t i = 0; i < pData->header.directMethodsSize; i++) {
    
            	  // art::mirror::ArtMethod是art下dex文件类经过内存加载后的描述结构体
            	  // 获取指定类的第i个直接类方法描述结构体art::mirror::ArtMethod
                  art::mirror::ArtMethod *method = klass->GetDirectMethod(i);
    
                  // 获取类方法的函数属性值AccessFlag
                  uint32_t ac = (method->GetAccessFlags()) & mask;
                  // 获取类方法的字节码偏移地址CodeItemOffset
                  uint32_t codeitem_off = method->GetCodeItemOffset();
    
    
                  // 通过类方法的DexMethodIndex获取到类方法的名称字符串
                  uint32_t dex_method_idx = method->GetDexMethodIndex();
                  const char * name = dex_file.GetMethodName(dex_file.GetMethodId(dex_method_idx));
    
                  // 判断dex文件中的类方法函数属性值AccessFlag与实际类运行时art::mirror::ArtMethod中函数属性值是否一致
                  if (ac != pData->directMethods[i].accessFlags)
                  {
                      #ifdef LOGI
                      LOG(INFO)<<"GOT IT direct method AF changed "<<name;
                      #endif
    
                      // 腾讯加固有这种情况,运行时native函数恢复为java函数
                      need_extra=true;
                      // 进行类方法函数属性的更正(以运行时为准)
                      pData->directMethods[i].accessFlags=ac;
                  }
    
                  // 根据类方法实际运行时的codeitem_off与dex文件中的codeOff偏移地址的不同,进行类方法字节码DexCode偏移地址的修正
                  if (codeitem_off != pData->directMethods[i].codeOff&&((codeitem_off>=start&&codeitem_off<=end)||codeitem_off==0)) {
                      #ifdef LOGI
                      LOG(INFO)<<"GOT IT direct method code changed "<<name;
                      #endif
                      need_extra=true;
    
                      // 以类方法实际运行时的codeitem_off为准,对dex文件中的类方法字节码DexCode的偏移地址进行修正
                      pData->directMethods[i].codeOff=codeitem_off;
                  }
    
                  // 类方法的字节码DexCode被存放在了OAT文件的文件末尾或者dex文件的ClassDef结构体表开始地址之前的位置
                  // 阿里加固出现过这种情况
                  if ((codeitem_off<start || codeitem_off>end) && codeitem_off!=0) {
    
                      #ifdef LOGI
                      LOG(INFO)<<"GOT IT direct method code changed "<<name;
                      #endif
    
                      need_extra=true;
                      // 统计将dex文件的类方法字节码DexFile::CodeItem从OAT文件结尾的位置开始存放(注意内存4字节对齐)
                      pData->directMethods[i].codeOff = total_pointer;
                      // 获取类方法字节码描述结构体指针DexFile::CodeItem
                      const DexFile::CodeItem * code = dex_file.GetCodeItem(codeitem_off);
    
                      // 类方法字节码描述结构体DexFile::CodeItem存放的起始地址
                      uint8_t *item=(uint8_t *) code;
                      int code_item_len = 0;
    
                      // 判断类方法是否有try...catch{}语句
                      if (code->tries_size_) {
    
                    	  // 获取有try...catch{}语句情况下,类方法的字节码DexFile::CodeItem结构体的结束地址
                          const byte *handler_data = (const byte *)(DexFile::GetTryItems(*code, code->tries_size_));
                          uint8_t * tail = codeitem_end(&handler_data);
    
                          // 有try...catch{}语句情况下,类方法的字节码DexFile::CodeItem结构体的字节大小
                          code_item_len = (int)(tail - item);
                      }else{
    
                    	  // 无try...catch{}语句情况下,类方法的字节码DexFile::CodeItem结构体的字节大小
                          code_item_len = 16+code->insns_size_in_code_units_*2;
                      }
    
                      // 将不在正常dex文件偏移存放位置的类方法字节码DexFile::CodeItem结构体的数据写入到xxxx/extra文件中
                      fwrite(item, 1, code_item_len, fp1);
                      fflush(fp1);
    
                      // 更新存放类方法字节码DexFile::CodeItem的文件偏移指针
                      total_pointer += code_item_len;
                      // 进行内存4字节对齐的填充处理
                      while (total_pointer&3) {
    
                          fwrite(&padding,1,1,fp1);
                          fflush(fp1);
    
                          total_pointer++;
                      }
                      #ifdef LOGI
                      LOG(INFO)<<"GOT IT total_pointer "<<total_pointer; 
                      #endif
                  }
    
              }
          }
    
          // 针对类虚方法的处理(和上面类直接方法的处理一样)
          if (pData->virtualMethods) {
    
        	  // 遍历类的虚方法
              for (uint32_t i=0; i<pData->header.virtualMethodsSize; i++) {
                  art::mirror::ArtMethod *method = klass->GetVirtualMethod(i);
                  uint32_t ac = (method->GetAccessFlags()) & mask;
                  uint32_t codeitem_off = method->GetCodeItemOffset();
                  uint32_t dex_method_idx = method->GetDexMethodIndex();
                  const char * name = dex_file.GetMethodName(dex_file.GetMethodId(dex_method_idx));
    
                  // 类方法函数属性accessFlags的修复
                  if (ac != pData->virtualMethods[i].accessFlags)
                  {
                      #ifdef LOGI
                      LOG(INFO)<<"GOT IT virtual method AF changed "<<name;
                      #endif
                      need_extra=true;
                      pData->virtualMethods[i].accessFlags=ac;
                  }
    
                  // 类方法codeitem_off的修正,以类方法运行时的数据为准
                  if (codeitem_off!=pData->virtualMethods[i].codeOff&&((codeitem_off>=start&&codeitem_off<=end)||codeitem_off==0)) {
                      #ifdef LOGI
                      LOG(INFO)<<"GOT IT virtual method code changed "<<name;
                      #endif
                      need_extra=true;
                      pData->virtualMethods[i].codeOff=codeitem_off;
                  }
    
                  // 类方法的字节码DexCode被存放在了OAT文件的文件末尾或者dex文件的ClassDef结构体表开始地址之前的位置
                  if ((codeitem_off<start || codeitem_off>end)&&codeitem_off!=0) {
    
                      #ifdef LOGI
                      LOG(INFO)<<"GOT IT virtual method code changed "<<name;
                      #endif
    
                      need_extra=true;
                      // 统计将dex文件的类方法字节码DexFile::CodeItem从OAT文件结尾的位置开始存放(注意内存4字节对齐)
                      pData->virtualMethods[i].codeOff = total_pointer;
                      const art::DexFile::CodeItem * code = dex_file.GetCodeItem(codeitem_off);
                      uint8_t *item=(uint8_t *) code;
                      int code_item_len = 0;
    
                      // 判断类方法是否有try...catch{}语句
                      if (code->tries_size_) {
    
                          const byte *handler_data = (const byte *)(DexFile::GetTryItems(*code, code->tries_size_));
                          uint8_t * tail=codeitem_end(&handler_data);
    
                          // 有try...catch{}语句情况下,类方法的字节码DexFile::CodeItem结构体的字节大小
                          code_item_len = (int)(tail-item);
                      }else{
    
                    	  // 无try...catch{}语句情况下,类方法的字节码DexFile::CodeItem结构体的字节大小
                          code_item_len = 16+code->insns_size_in_code_units_*2;
                      }
    
                      // 将不在正常dex文件偏移存放位置的类方法字节码DexFile::CodeItem结构体的数据写入到xxxx/extra文件中
                      fwrite(item,1,code_item_len,fp1);
                      fflush(fp1);
    
                      // 更新存放类方法字节码DexFile::CodeItem的文件偏移指针
                      total_pointer+=code_item_len;
                      // 进行内存4字节对齐的填充处理
                      while (total_pointer&3) {
                          fwrite(&padding,1,1,fp1);
                          fflush(fp1);
                          total_pointer++;
                      }
    
                      #ifdef LOGI
                      LOG(INFO)<<"GOT IT total_pointer "<<total_pointer; 
                      #endif
                  }
    
              }
          }
    
    // 上面是针对dex文件类方法的字节码DexFile::CodeItem的修正处理并保存到xxxx/extra文件中
    // 以及对dex文件的ClassDef对应的ClassData的数据修正处理(以运行时的类描述信息为准)
    
    // 下面是针对dex文件的
    classdef:
           DexFile::ClassDef *temp = (DexFile::ClassDef*) malloc(sizeof(DexFile::ClassDef));
           if (!temp) {
               continue;
           }
    
    
           temp->class_idx_ = class_def.class_idx_;
           temp->pad1_=class_def.pad1_;
           temp->pad2_=class_def.pad2_;
           temp->access_flags_=class_def.access_flags_;
           temp->annotations_off_= class_def.annotations_off_;
           temp->class_data_off_=class_def.class_data_off_;
           temp->interfaces_off_=class_def.interfaces_off_;
           temp->source_file_idx_=class_def.source_file_idx_;
           temp->static_values_off_=class_def.static_values_off_;
           temp->superclass_idx_=class_def.superclass_idx_;
    
           if (pass) {
    
        	   // Android系统类和无效类的情况处理
               temp->class_data_off_=0;
               temp->annotations_off_=0;
           }
    
           uint8_t *p = (uint8_t *)temp;
           if (need_extra) {
    
        	   // dex文件的类DexClassData需要修正情况
               int class_data_len = 0;
               // 将DexClassData所表示的类数据pData进行leb128编码写入到申请的内存空间中
               // class_data_len为保存DexClassData进行leb128编码后的数据长度
               uint8_t *out = dexEncodeClassData(pData, class_data_len);
               if (!out) {
                   continue;
               }
    
               // 修正DexFile::ClassDef中指向DexClassData的文件偏移指针
               temp->class_data_off_ = total_pointer;
               #ifdef LOGI
               LOG(INFO)<<"GOT IT write extra";
               #endif
    
               // 将正确修正后的dex文件的类DexClassData数据保存到xxxx/extra文件中
               fwrite(out, 1, class_data_len, fp1);
               fflush(fp1);
    
               // 更新在xxxx/extra文件中存放DexClassData或者DexFile::CodeItem的文件偏移指针
               total_pointer += class_data_len;
               // 内存4字节对齐的填充处理
               while (total_pointer&3) {
    
                   fwrite(&padding, 1, 1, fp1);
                   fflush(fp1);
    
                   total_pointer++;
               }
               #ifdef LOGI
               LOG(INFO)<<"GOT IT total_pointer "<<total_pointer; 
               #endif
               free(out);
    
           } else {
               if (pData) {
                   free(pData);
               }
           }
    
           #ifdef LOGI
           LOG(INFO)<<"GOT IT write classdef";
           #endif
    
           // 将根据上面xxxx/extra文件中存放的DexClassData数据修正的DexFile::ClassDef结构体数据保存到xxxx/classdef文件中
           fwrite(p, sizeof(DexFile::ClassDef), 1, fp);
           fflush(fp);
           free(temp);
      }
    
      // 释放锁
      Locks::mutator_lock_->SharedUnlock(self);
      // 关闭文件
      fclose(fp1);
      fclose(fp);
    
      #ifdef LOGI
      LOG(INFO)<<"GOT IT ClassDumped";
      #endif
      self->SetState(kSleeping);
      // 取消对art虚拟机线程的附加
      runtime->DetachCurrentThread();
    
      // 需要脱壳的dex文件的内存dump已经全部完成,后面是dump后的dex文件重组处理
    
      // 拼接字符串得到文件路径xxxx/whole.dex
      strcpy(path,dumppath);
      strcat(path,"whole.dex");
    
      // 打开文件xxxx/whole.dex(用以存放dump重组后的dex文件)
      fp = fopen(path,"wb+");
      // 设置文件指针在文件开头
      rewind(fp);
    
      int fd=-1;
      int r=-1;
      int len=0;  
      char *addr=NULL;
      struct stat st;
    
      strcpy(path, dumppath);
      strcat(path,"part0");
      // 打开文件xxxx/part0
      fp1 = fopen(path,"rb");
      char reg=0;
    
      // 读取文件xxxx/part0中的数据内容保存到文件xxxx/whole.dex中
      for (uint32_t i = 0; i < 16; i++) {
    
          fread(&reg, 1, 1, fp1);
          fwrite(&reg, 1, 1, fp);
          fflush(fp);
      }
      fclose(fp1);
    
      strcpy(path,dumppath);
      strcat(path,"part1");
      // 打开文件xxxx/part1
      fd = open(path, O_RDONLY, 0666);
      if (fd == -1) {
          return NULL;
      }
    
      // 获取文件xxxx/part1的数据大小
      r = fstat(fd, &st);
      if(r == -1){
          close(fd);  
          return NULL;
      }
      len = st.st_size;
      // 为文件xxxx/part1创建内存映射进行文件的读取操作
      addr = (char*)mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
    
      // 将文件xxxx/part1的数据内容写入保存到文件xxxx/whole.dex中
      fwrite(addr, 1, len, fp);
      // 刷新文件流
      fflush(fp);
      // 取消内存映射
      munmap(addr,len);
      close(fd);
    
      #ifdef LOGI
      LOG(INFO)<<"GOT IT part1 over ";
      #endif
    
      strcpy(path, dumppath);
      strcat(path, "classdef");
      // 打开文件xxxx/classdef
      fd = open(path, O_RDONLY, 0666);
      if (fd==-1) {
          return NULL;
      }
    
      // 获取文件xxxx/classdef的数据大小
      r = fstat(fd,&st);
      if(r==-1){  
          close(fd);  
          return NULL;
      }
      len=st.st_size;
    
      // 为文件xxxx/classdef创建内存映射实现文件的读取操作
      addr = (char*)mmap(NULL,len,PROT_READ,MAP_PRIVATE,fd,0);
    
      // 将文件xxxx/classdef的数据内容写入到文件xxxx/whole.dex中
      fwrite(addr,1,len,fp);
      fflush(fp);
      munmap(addr,len);
      close(fd);
    
      #ifdef LOGI
      LOG(INFO)<<"GOT IT classdef over ";
      #endif
    
      strcpy(path,dumppath);
      strcat(path,"data");
    
      // 打开文件xxxx/data
      fd = open(path, O_RDONLY, 0666);
      if (fd==-1) {
          return NULL;
      }
    
      // 获取文件xxxx/data的文件大小
      r = fstat(fd, &st);
      if(r==-1){  
          close(fd);  
          return NULL;
      }
      len=st.st_size;
    
      // 为文件xxxx/data创建文件内存映射
      addr=(char*)mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
      // 将文件xxxx/data的数据内容写入到文件xxxx/whole.dex中
      fwrite(addr,1,len,fp);
      fflush(fp);
      munmap(addr,len);
      close(fd);
    
      #ifdef LOGI
      LOG(INFO)<<"GOT IT data over ";
      #endif
    
      // 对 xxxx/data 进行内存4字节对齐的'0'填充处理
      while (inc>0) {
          fwrite(&padding, 1, 1, fp);
          fflush(fp);
          inc--;
      }
    
      strcpy(path, dumppath);
      strcat(path,"extra");
      // 打开文件xxxx/extra
      fd = open(path,O_RDONLY,0666);
      if (fd==-1) {
          return NULL;
      }
    
      // 获取文件xxxx/extra的文件大小
      r = fstat(fd,&st);
      if(r == -1){
          close(fd);  
          return NULL;
      }
      len=st.st_size;
    
      // 为文件xxxx/extra创建文件内存映射实现文件的读取操作
      addr = (char*)mmap(NULL,len,PROT_READ,MAP_PRIVATE,fd,0);
      // 将文件xxxx/extra的数据内容保存写入到文件xxxx/whole.dex中
      fwrite(addr, 1, len, fp);
      fflush(fp);
      munmap(addr,len);
      close(fd);
    
      /****
      xxxx/part0
      xxxx/part1
      xxxx/classdef    (dex文件的重组)-------->    xxxx/whole.dex
      xxxx/data
      xxxx/extra
      *****/
    
      #ifdef LOGI
      LOG(INFO)<<"GOT IT extra over ";
      #endif
    
      fclose(fp);
      delete path;
    
      #ifdef LOGI
      time = MilliTime();
      LOG(INFO)<<"GOT IT end "<<time<<" ms";
      #endif
    
      return NULL;
    }
    //-----------------------added end-----------------------//
    
    
    // art下的类加载函数
    mirror::Class* ClassLinker::DefineClass(const char* descriptor,
                                            mirror::ClassLoader* class_loader,
                                            const DexFile& dex_file,
                                            const DexFile::ClassDef& dex_class_def) {
    
    //-----------------------added begin-----------------------//
      int uid=::art::GetUid();
      
      // compiler用来指定当前要创建的ART虚拟机是用来将DEX字节码编译成本地机器指令的
      // 因此排除掉Runtime::Current()->IsCompiler()为true的优化dex文件的art虚拟机情况
      // 参考:http://blog.csdn.net/Luoshengyang/article/details/39307813
      if (Runtime::Current()->IsCompiler()) {
          goto there;
      }
    
      // 排除掉系统进程(uid == 0的情况)
      if (uid) {
          if (readable) {
    		  
    		  // 锁
              pthread_mutex_lock(&read_mutex);
              if (readable) {
    			  
                  readable=false;
                  pthread_mutex_unlock(&read_mutex);
                  pthread_t read_thread;
    			  
    			  // 创建线程,读取脱壳配置文件/data/dexname的信息
    			  // 获取需要脱壳dex文件的加载路径dexname以及存放脱壳后dex文件dump文件路径dumppath
                  pthread_create(&read_thread, NULL, ReadThread, NULL);
    			  
              }else{
                  pthread_mutex_unlock(&read_mutex);
              }
          }
      }
      
      // 排除掉uid==0的进程以及被dump的加固dex文件的内存加载路径不能为空
      if(uid && strcmp(dexname,"")){
    	 
    	 // dex_file.GetLocation().c_str()获取到art下dex文件的加载路径
    	 // 通过加固dex文件的加载路径判断是否是需要脱壳的dex文件
         char * res = strstr(dex_file.GetLocation().c_str(), dexname);
         if (res && flag) {
    		 
            pthread_mutex_lock(&mutex);
            if (flag) {
    			
               flag=false;
               pthread_mutex_unlock(&mutex);
    
               char * temp = new char[100];
               strcpy(temp, dumppath);
    		   // 拼接字符串得到文件路径 xxxx/part0
               strcat(temp,"part0");
    		   
    		   // 打开文件xxxx/part0
               FILE *fp = fopen(temp, "wb+");
    
               // ART下的OAT文件是一个私有的ELF文件格式
    		   // 获取OAT文件的内存映射的起始地址即dex优化后私有ELF文件的内存映射地址
               const byte *addr = dex_file.Begin();
    
               int length = 16;
               // 将OAT文件(即私有ELF文件的)前16字节数据保存到xxxx/part0中
               for (int i = 0; i < 16; i++) {
    			   
                   fwrite(addr+i, 1 ,1 ,fp);
                   fflush(fp);
               }
               fclose(fp);
    		   
               strcpy(temp, dumppath);
    		   // 得到路径字符串xxxx/part1
               strcat(temp,"part1");
    		   
    		   // 打开文件xxxx/part1
               fp = fopen(temp, "wb+");
               // 内存地址指针移动到OAT文件即私有ELF文件的第17个字节的位置
               addr = dex_file.Begin() + 16;
               // 获取到OAT文件里即私有ELF文件的第17个字节到dex文件class_defs_off_开始地址之间的数据长度
               length = dex_file.class_defs_off_ - 16;
    
               // 将OAT文件里即私有ELF文件的第17个字节到dex文件class_defs_off_开始地址的数据写入到xxxx/part1中
               fwrite(addr, 1, length, fp);
               // 刷新文件流
               fflush(fp);
               fclose(fp);
    		   
               // 拼接得到字符串xxxx/data
               strcpy(temp, dumppath);
               strcat(temp,"data");
    
               // 打开文件xxxx/data
               fp = fopen(temp,"wb+");
    
               // 将OAT文件里的dex文件指针移动到dex文件的ClassDef结构体表Table的结尾位置
               addr = dex_file.Begin()+dex_file.class_defs_off_+sizeof(DexFile::ClassDef)*dex_file.NumClassDefs();
               // 获取OAT文件里dex文件的ClassDef结构体表Table的结尾位置到OAT文件的结尾数据长度
               length=dex_file.Size()-dex_file.class_defs_off_-sizeof(DexFile::ClassDef)*dex_file.NumClassDefs();
    
               // 将OAT文件里dex文件的ClassDef结构体表Table的结束位置到OAT文件的结尾数据写入到xxxx/data文件中
               fwrite(addr, 1, length, fp);
               fflush(fp);
               fclose(fp);
               delete temp;
    
               // 当前dex文件所在的class_loader
               param.class_loader = class_loader;
               // 当前dex文件的内存加载的镜像描述结构体
               param.dex_file = &dex_file;
               // 当前dex文件所在的ClassLinker
               param.cl = this;
    
               pthread_t dumpthread;
               // 创建线程对需要脱壳dex的OAT文件里的类数据进行内存dump处理
               pthread_create(&dumpthread, NULL, DumpClass, (void*)&param);
    
             }else{
               pthread_mutex_unlock(&mutex);
            }
         }
      }
    //-----------------------added end-----------------------//
    
    
    there:
      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)));
      }
      if (UNLIKELY(klass.get() == NULL)) {
        CHECK(self->IsExceptionPending());  // Expect an OOME.
        return NULL;
      }
      klass->SetDexCache(FindDexCache(dex_file));
      LoadClass(dex_file, dex_class_def, klass, class_loader);
      // Check for a pending exception during load
      if (self->IsExceptionPending()) {
        klass->SetStatus(mirror::Class::kStatusError, self);
        return NULL;
      }
      ObjectLock lock(self, klass.get());
      klass->SetClinitThreadId(self->GetTid());
      {
        // 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);
        }
      }
      // Finish loading (if necessary) by finding parents
      CHECK(!klass->IsLoaded());
      if (!LoadSuperAndInterfaces(klass, dex_file)) {
        // Loading failed.
        klass->SetStatus(mirror::Class::kStatusError, self);
        return NULL;
      }
      CHECK(klass->IsLoaded());
      // Link the class (if necessary)
      CHECK(!klass->IsResolved());
      if (!LinkClass(klass, NULL, self)) {
        // Linking failed.
        klass->SetStatus(mirror::Class::kStatusError, self);
        return NULL;
      }
      CHECK(klass->IsResolved());
    
      /*
       * We send CLASS_PREPARE events to the debugger from here.  The
       * definition of "preparation" is creating the static fields for a
       * class and initializing them to the standard default values, but not
       * executing any code (that comes later, during "initialization").
       *
       * We did the static preparation in LinkClass.
       *
       * The class has been prepared and resolved but possibly not yet verified
       * at this point.
       */
      Dbg::PostClassPrepare(klass.get());
    
      return klass.get();
    }                                                                                                                          
    
    

    展开全文
  • Android上的ART虚拟机

    千次阅读 2018-10-15 11:13:27
    本会讲解Android上的ART虚拟机。 我的博客中,还有另外两篇关于Android虚拟机的文章也可以配套阅读: Android上的Dalvik虚拟机 AndroidART虚拟机Android 5.0(Lollipop)开始,Android Runtime(下文简称ART...

    本会讲解Android上的ART虚拟机。

    我的博客中,还有另外两篇关于Android虚拟机的文章也可以配套阅读:

    从Android 5.0(Lollipop)开始,Android Runtime(下文简称ART)就彻底代替了原先的Dalvik,成为Android系统上新的虚拟机。

    这篇文章我们就来详细了解一下ART虚拟机。

    ART VS. Dalvik

    Dalvik虚拟机是2008年跟随Android系统一起发布的。当时的移动设备的系统内存只有64M左右,CPU频率在250~500MHz之间。这个硬件水平早已发生了巨大变化。随着智能设备的兴起,这些年移动芯片的性能每年都有大幅提升。如今的智能手机内存已经有6G甚至8G至多。CPU也已经步入了64位的时代,频率高达2.0 GHz甚至更高。硬件的更新,常常也伴随着软件的换代。因此,Dalvik虚拟机被淘汰也是情理之中的事情。

    Dalvik之所以要被ART替代包含下面几个原因:

    • Dalvik是为32位设计的,不适用于64位CPU。
    • 单纯的字节码解释加JIT编译的执行方式,性能要弱于本地机器码的执行。
    • 无论是解释执行还是JIT编译都是单次运行过程中发生,每运行一次都可能需要重新做这些工作,这样做太浪费资源。
    • 原先的垃圾回收机制不够好,会导致卡顿。

    很显然,ART虚拟机对上面提到的这些地方做了改进。除了支持64位不必说,最主要的是下面两项改进:

    • AOT编译:Ahead-of-time(AOT)是相对于Just-in-time(JIT)而言的。JIT是在运行时进行字节码到本地机器码的编译,这也是为什么Java普遍被认为效率比C++差的原因。无论是解释器的解释还是运行过程中即时编译,都比C++编译出的本地机器码执行多了一个耗费时间的过程。而AOT就是向C++编译过程靠拢的一项技术:当APK在安装的时候,系统会通过一个名称为dex2oat的工具将APK中的dex文件编译成包含本地机器码的oat文件存放下来。这样做之后,在程序执行的时候,就可以直接使用已经编译好的机器码以加快效率。
    • 垃圾回收的改进:GC(Garbage Collection)是虚拟机非常重要的一个特性,因为它的实现好坏会影响所有在虚拟机上运行的应用。GC实现得不好可能会导致画面跳跃,掉帧,UI响应过慢等问题。ART的垃圾回收机制相较于Dalvik虚拟机有如下改进:

      • 将GC的停顿由2次改成1次
      • 在仅有一次的GC停顿中进行并行处理
      • 在特殊场景下,对于近期创建的具有较短生命的对象消耗更少的时间进行垃圾回收
      • 改进垃圾收集的工效,更频繁的执行并行垃圾收集
      • 对于后台进程的内存在垃圾回收过程进行压缩以解决碎片化的问题

    AOT编译是在应用程序安装时就进行的工作,下图描述了Dalvik虚拟机与(Android 5.0上的)ART虚拟机在安装APK时的区别:

    art_vs_dalvik.png

    两种虚拟机上安装APK时的流程

    从这幅图中我们看到:

    • 在Dalvik虚拟机上,APK中的Dex文件在安装时会被优化成odex文件,在运行时,会被JIT编译器编译成native代码。
    • 而在ART虚拟机上安装时,Dex文件会直接由dex2oat工具翻译成oat格式的文件,oat文件中既包含了dex文件中原先的内容,也包含了已经编译好的native代码。

    dex2oat生成的oat文件在设备上位于/data/dalvik-cache/目录下。同时,由于32位和64位的机器码有所区别,因此这个目录下还会通过子文件夹对oat文件进行分类。例如,手机上通常会有下面两个目录:

    • /data/dalvik-cache/arm/
    • /data/dalvik-cache/arm64/

    接下来,我们就以oat文件为起点来了解ART虚拟机。

    OAT文件格式

    OAT文件遵循ELF格式。ELF是Unix系统上可执行文件,目标文件,共享库和Core dump文件的标准格式。ELF全称是Executable and Linkable Format,该文件格式如下图所示:

    ELF_layout.png

    ELF文件格式

    每个ELF文件包含一个ELF头信息,以及文件数据。

    头信息描述了整个文件的基本属性,例如ELF文件版本,目标机器型号,程序入口地址等。

    文件数据包含三种类型的数据:

    • 程序表(Program header table):该数据会影响系统加载进程的内存地址空间
    • 段表(Section header table):描述了ELF文件中各个段的(Section)信息
    • 若干个段。常见的段包括:

      • 代码段(.text):程序编译后的指令
      • 只读数据段(.rodata):只读数据,通常是程序里面的只读变量和字符串常量
      • 数据段:(.data):初始化了的全局静态变量和局部静态变量
      • BSS端(.bss):未初始化的全局变量和局部静态变量

    关于ELF文件格式的详细说明可以参见维基百科:Executable and Linkable Format ,这里不再深入讨论。

    下面我们再来看一下OAT文件的格式:

    art_oat_file.png

    OAT文件格式

    从这个图中我们看到,OAT文件中包含的内容有:

    • ELF Header:ELF头信息。
    • oatdata symbol:oatdata符号,其地址指向了OAT头信息。
    • Header:Oat文件的头信息,详细描述了Oat文件中的内容。例如:Oat文件的版本,Dex文件个数,指令集等等信息。Header,Dex File数组以及Class Metadata数组都位于ELF的只读数据段(.rodata)中。
    • Dex File数组:生成该Oat文件的Dex文件,可能包含多个。
    • Class Metadata数组:Dex中包含的类的基本信息,可能包含多个。通过其中的信息可以索引到编译后的机器码。
    • 编译后的方法代码数组:每个方法编译后对应的机器码,可能包含多个。这些内容位于代码段(.text)中。

    我们可以通过/art/目录下的这些源码文件来详细了解Oat文件的结构:

    • compiler/oat_witer.h
    • compiler/oat_writer.cc
    • dex2oat/dex2oat.cc
    • runtime/oat.h
    • runtime/oat.cc
    • runtime/oat_file.h
    • runtime/oat_file.cc
    • runtime/image.h
    • runtime/image.cc

    Oat文件的主要组成结构如下表所示:

    字段名称说明
    OatHeaderOat文件头信息
    OatDexFile数组Dex文件的详细信息
    Dex数组.dex文件的拷贝
    TypeLookupTable数组用来辅助查找Dex文件中的类
    ClassOffsets数组OatDexFile中每个类的偏移表
    OatClass数组每个类的详细信息
    padding如果需要,通过填充padding来让后面的内容进行页面对齐
    OatMethodHeaderOat文件中描述方法的头信息
    MethodCode类的方法代码,OatMethodHeader和MethodCode会交替出现多次

    dex文件可以通过dexdump工具进行分析。oat文件也有对应的dump工具,这个工具就叫做oatdump

    通过adb shell连上设备之后,可以通过输入oatdump来查看该命令的帮助:

    angler:/ # oatdump
    No arguments specified
    Usage: oatdump [options] ...
        Example: oatdump --image=$ANDROID_PRODUCT_OUT/system/framework/boot.art
        Example: adb shell oatdump --image=/system/framework/boot.art
    
      --oat-file=<file.oat>: specifies an input oat filename.
          Example: --oat-file=/system/framework/boot.oat
    
      --image=<file.art>: specifies an input image location.
          Example: --image=/system/framework/boot.art
    
      --app-image=<file.art>: specifies an input app image. Must also have a specified
     boot image and app oat file.
    
    ...

    例如:可以通过--list-classes命令参数来列出dex文件中的所有类:

    oatdump --list-classes --oat-file=/data/dalvik-cache/arm64/system@app@Calendar@Calendar.apk@classes.dex

    boot.oat 与 boot.art

    任何应用程序都不是孤立存在的,几乎所有应用程序都会依赖Android Framework中提供的基础类,例如ActivityIntentParcel等类。所以在应用程序的代码中,自然少不了对于这些类的引用。因此,在上图中我们看到,代码(.text)段中的的代码会引用Framework Image和Framrwork Code中的内容。

    考虑到几乎所有应用都存在这种引用关系,在运行时都会依赖于Framework中的类,因此系统如何处理这部分逻辑就是非常重要的了,因为这个处理的方法将影响到所有应用程序。

    在AOSP编译时,会将所有这些公共类放到专门的一个Oat文件中,这个文件就是:boot.oat。与之配合的还有一个boot.art文件。

    我们可以在设备上的/data/dalvik-cache/[platform]/目录下找到这两个文件:

    -rw-r--r-- 1 root   root      11026432 1970-06-23 01:35 system@framework@boot.art
    -rw-r--r-- 1 root   root      31207992 1970-06-23 01:35 system@framework@boot.oat

    boot.art中包含了指向boot.oat中方法代码的指针,它被称之为启动镜像(Boot Image),并且被加载的位置是固定的。boot.oat被加载的地址紧随着boot.art。

    包含在启动镜像中的类是一个很长的列表,它们在这个文件中配置:frameworks/base/config/preloaded-classes。从Android L(5.0)之后的版本开始,设备厂商可以在设备的device.mk中通过PRODUCT_DEX_PREOPT_BOOT_FLAGS这个变量来添加配置到启动镜像中的类。像这样:

    PRODUCT_DEX_PREOPT_BOOT_FLAGS += --image-classes=<filename>

    系统在初次启动时,会根据配置的列表来生成boot.oat和boot.art两个文件(读者也可以手动将/data/dalvik-cache/目录下文件都删掉来让系统重新生成),生成时的相关日志如下:

    1249:10-04 04:25:45.700   530   530 I art     : GenerateImage: /system/bin/dex2oat --image=/data/dalvik-cache/arm64/system@framework@boot.art --dex-file=/system/framework/core-oj.jar --dex-file=/system/framework/core-libart.jar --dex-file=/system/framework/conscrypt.jar --dex-file=/system/framework/okhttp.jar --dex-file=/system/framework/core-junit.jar --dex-file=/system/framework/bouncycastle.jar --dex-file=/system/framework/ext.jar --dex-file=/system/framework/framework.jar --dex-file=/system/framework/telephony-common.jar --dex-file=/system/framework/voip-common.jar --dex-file=/system/framework/ims-common.jar --dex-file=/system/framework/apache-xml.jar --dex-file=/system/framework/org.apache.http.legacy.boot.jar --oat-file=/data/dalvik-cache/arm64/system@framework@boot.oat --instruction-set=arm64 --instruction-set-features=smp,a53 --base=0x6f96c000 --runtime-arg -Xms64m --runtime-arg -Xmx64m --compiler-filter=verify-at-runtime --image-classes=/system/etc/preloaded-classes --compiled-classes=/system/etc/compiled-classes -j4 --instruction-set-variant=cor

    Dalvik到ART的切换

    ART虚拟机是在Android 5.0上正式启用的。实际上在Android 4.4上,就已经内置了ART虚拟机,只不过默认没有启用。但是Android在系统设置中提供了选项让用户可以切换。那么我们可能会很好奇,这里到底是如何进行虚拟机的切换的呢?

    要知道这里是如何实现的,我们可以从设置界面的代码入手。Android 4.4上是在开发者选项中提供了切换虚拟机的入口。其实现类是DevelopmentSettings

    如果你查看相关代码你就会发现,这里切换的过程其实就是设置了一个属性值,然后将系统直接重启。相关代码如下:

    // DevelopmentSettings.java
    
    private static final String SELECT_RUNTIME_PROPERTY = "persist.sys.dalvik.vm.lib";
    ...
    
    SystemProperties.set(SELECT_RUNTIME_PROPERTY, newRuntimeValue);
    pokeSystemProperties();
    PowerManager pm = (PowerManager)
            context.getSystemService(Context.POWER_SERVICE);
    pm.reboot(null);

    那么接下来我们要关注的自然是persist.sys.dalvik.vm.lib这个属性被哪里读取到了。

    回顾一下AndroidRuntime::start方法,读者可能会发现这个方法中有两行代码我们前面看到了却没有关注过:

    // AndroidRuntime.cpp
    
    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;
        }

    那就是下面这两行。实际上,它们就是切换虚拟机的关键。

    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);

    JniInvocation这个结构是在/libnativehelper/目录下定义的。对于虚拟机的选择也就是在这里确定的。persist.sys.dalvik.vm.lib属性的值实际上是so文件的路径,可能是libdvm.so,也可能是libart.so,前者是Dalvik虚拟机的实现,而后者就是ART虚拟机的实现。

    JniInvocation::Init方法代码如下

    // JniInvocation.cpp
    
    bool JniInvocation::Init(const char* library) {
    #ifdef HAVE_ANDROID_OS
      char default_library[PROPERTY_VALUE_MAX];
      property_get("persist.sys.dalvik.vm.lib", default_library, "libdvm.so"); ①
    #else
      const char* default_library = "libdvm.so";
    #endif
      if (library == NULL) {
        library = default_library;
      }
    
      handle_ = dlopen(library, RTLD_NOW); ②
      if (handle_ == NULL) { ③
        ALOGE("Failed to dlopen %s: %s", library, dlerror());
        return false;
      }
      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. 获取persist.sys.dalvik.vm.lib属性的值(可能是libdvm.so,或者是libart.so)
    2. 通过dlopen加载这个so库
    3. 如果加载失败则报错
    4. 确定so中包含了JNI接口需要的三个函数,它们分别是:JNI_GetDefaultJavaVMInitArgsJNI_CreateJavaVMJNI_GetCreatedJavaVMs

    而每当用户通过设置修改了persist.sys.dalvik.vm.lib属性值之后,便会改变这里加载的so库。由此导致了虚拟机的切换,如下图所示:

    dalvik_art_switch.png

    Dalvik与ART虚拟机的切换

    ART虚拟机的启动过程

    ART虚拟机的代码位于下面这个路径:

    /art/runtime

    前一篇文章中我们看到,JNI_CreateJavaVM是由Dalvik虚拟机提供的用来创建虚拟机实例的函数。并且在JniInvocation::Init方法会检查,ART虚拟机的实现中也要包含这个函数。

    实际上,这个函数是由JNI标准接口定义的,提供JNI功能的虚拟机都需要提供这个函数用来从native代码中启动虚拟机。

    因此要知道ART虚拟机的启动逻辑,我们需要从ART的JNI_CreateJavaVM函数看起。

    这个函数代码如下:

    // java_vm_ext.cc
    
    extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
      ScopedTrace trace(__FUNCTION__);
      const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args);
      if (IsBadJniVersion(args->version)) {
        LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version;
        return JNI_EVERSION;
      }
      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)) {
        return JNI_ERR;
      }
    
      // Initialize native loader. This step makes sure we have
      // everything set up before we start using JNI.
      android::InitializeNativeLoader();
    
      Runtime* runtime = Runtime::Current();
      bool started = runtime->Start();
      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;
    }

    这段代码中牵涉的逻辑较多,这里就不贴出更多的代码了。下图总结了ART虚拟机的启动过程:

    ART虚拟机的启动过程

    图中的步骤说明如下:

    • Runtime::Create: 创建Runtime实例

      • Runtime::Init:对Runtime进行初始化

        • runtime_options.GetOrDefault: 读取启动参数
        • new gc::Heap: 创建虚拟机的堆,Java语言中通过new创建的对象都位于Heap中。

          • ImageSpace::CreateBootImage:初次启动会创建Boot Image,即boot.art
          • garbage_collectors_.push_back: 创建若干个垃圾回收器并添加到列表中,见下文垃圾回收部分
        • Thread::Startup: 标记线程为启动状态
        • Thread::Attach: 设置当前线程为虚拟机主线程
        • LoadNativeBridge: 通过dlopen加载native bridge,见下文。
    • android::InitializeNativeLoader: 初始化native loader,见下文。
    • runtime = Runtime::Current: 获取当前Runtime实例
    • runtime->Start: 通过Start接口启动虚拟机

      • InitNativeMethods: 初始化native方法

        • RegisterRuntimeNativeMethods: 注册dalvik.system,java.lang,libcore.util,org.apache.harmony以及sun.misc几个包下类的native方法
        • WellKnownClasses::Init: 预先缓存一些常用的类,方法和字段。
        • java_vm_->LoadNativeLibrary:加载libjavacore.so以及libopenjdk.so两个库
        • WellKnownClasses::LateInit: 预先缓存一些前面无法缓存的方法和字段
      • Thread::FinishStartup: 完成初始化
      • CreateSystemClassLoader: 创建系统类加载器
      • StartDaemonThreads: 调用java.lang.Daemons.start方法启动守护线程

        • java.lang.Daemons.start: 启动了下面四个Daemon:

          • ReferenceQueueDaemon
          • FinalizerDaemon
          • FinalizerWatchdogDaemon
          • HeapTaskDaemon

    从这个过程中我们看到,ART虚拟机的启动,牵涉到了:创建堆;设置线程;加载基础类;创建系统类加载器;以及启动虚拟机需要的daemon等工作。

    除此之外,这里再对native bridge以及native loader做一些说明。这两个模块的源码位于下面这个路径:

    /system/core/libnativebridge/
    /system/core/libnativeloader/
    • native bridge:我们知道,Android系统主要是为ARM架构的CPU为开发的。因此,很多的库都是为ARM架构的CPU编译的。但是如果将Android系统移植到其他平台(例如:Intel的x86平台),就会出现很多的兼容性问题(ARM的指令无法在x86 CPU上执行)。而这个模块的作用就是:在运行时动态的进行native指令的转译,即:将ARM的指令转译成其他平台(例如x86)的指令,这也是为什么这个模块的名字叫做“Bridge”。
    • native loader:顾名思义,这个模块专门负责native库的加载。一旦应用程序使用JNI调用,就会牵涉到native库的加载。Android系统自Android 7.0开始,加强了应用程序对于native库链接的限制:只有系统明确公开的库才允许应用程序链接。这么做的目的是为了减少因为系统升级导致了二进制库不兼容(例如:某个库没有了,或者函数符号变了),从而导致应用程序crash的问题。而这个限制的工作就是在这个模块中完成的。系统公开的二进制库在这个文件(设备上的路径)中列了出来:/etc/public.libraries.txt。除此之外,厂商也可能会公开一些扩展的二进制库,厂商需要将这些库放在vendor/lib(或者/vendor/lib64)目录下,同时将它们列在/vendor/etc/public.libraries.txt中。

    内存分配

    应用程序在任何时候都可能会创建对象,因此虚拟机对于内存分配的实现方式会严重影响应用程序的性能。

    原先Davlik虚拟机使用的是传统的 dlmalloc 内存分配器进行内存分配。这个内存分配器是Linux上很常用的,但是它没有为多线程环境做过优化,因此Google为ART虚拟机开发了一个新的内存分配器:RoSalloc,它的全称是Rows of Slots allocator。RoSalloc相较于dlmalloc来说,在多线程环境下有更好的支持:在dlmalloc中,分配内存时使用了全局的内存锁,这就很容易造成性能不佳。而在RoSalloc中,允许在线程本地区域存储小对象,这就是避免了全局锁的等待时间。ART虚拟机中,这两种内存分配器都有使用。

    要了解ART虚拟机对于内存的分配和回收,我们需要从Heap入手,/art/runtime/gc/ 目录下的代码对应了这部分逻辑的实现。

    在前面讲解ART虚拟机的启动过程中,我们已经看到过,ART虚拟机启动中便会创建Heap对象。其实在Heap的构造函数,还会创建下面两类对象:

    • 若干个Space对象:Space用来响应应用程序对于内存分配的请求
    • 若干个GarbageCollector对象:GarbageCollector用来进行垃圾收集,不同的GarbageCollector执行的策略不一样,见下文“垃圾回收”

    Space有下面几种类型:

    enum SpaceType {
      kSpaceTypeImageSpace,
      kSpaceTypeMallocSpace,
      kSpaceTypeZygoteSpace,
      kSpaceTypeBumpPointerSpace,
      kSpaceTypeLargeObjectSpace,
      kSpaceTypeRegionSpace,
    };

    下面一幅图是Space的具体实现类。从这幅图中我们看到, Space主要分为两类:

    • 一类是内存地址连续的,它们是ContinuousSpace的子类
    • 还有一类是内存地址不连续的,它们是DiscontinuousSpace的子类

    art_space.png

    ART虚拟机中的Space

    在一个运行的ART的虚拟机中,上面这些Space未必都会创建。有哪些Space会创建由ART虚拟机的启动参数决定。Heap对象中会记录所有创建的Space,如下所示:

    // heap.h
    
    // All-known continuous spaces, where objects lie within fixed bounds.
    std::vector<space::ContinuousSpace*> continuous_spaces_ GUARDED_BY(Locks::mutator_lock_);
    
    // All-known discontinuous spaces, where objects may be placed throughout virtual memory.
    std::vector<space::DiscontinuousSpace*> discontinuous_spaces_ GUARDED_BY(Locks::mutator_lock_);
    
    // All-known alloc spaces, where objects may be or have been allocated.
    std::vector<space::AllocSpace*> alloc_spaces_;
    
    // A space where non-movable objects are allocated, when compaction is enabled it contains
    // Classes, ArtMethods, ArtFields, and non moving objects.
    space::MallocSpace* non_moving_space_;
    
    // Space which we use for the kAllocatorTypeROSAlloc.
    space::RosAllocSpace* rosalloc_space_;
    
    // Space which we use for the kAllocatorTypeDlMalloc.
    space::DlMallocSpace* dlmalloc_space_;
    
    // The main space is the space which the GC copies to and from on process state updates. This
    // space is typically either the dlmalloc_space_ or the rosalloc_space_.
    space::MallocSpace* main_space_;
    
    // The large object space we are currently allocating into.
    space::LargeObjectSpace* large_object_space_;

    Heap类的AllocObject是为对象分配内存的入口,这是一个模板方法,该方法代码如下:

    // heap.h
    
    // Allocates and initializes storage for an object instance.
    template <bool kInstrumented, typename PreFenceVisitor>
    mirror::Object* AllocObject(Thread* self,
                             mirror::Class* klass,
                             size_t num_bytes,
                             const PreFenceVisitor& pre_fence_visitor)
     SHARED_REQUIRES(Locks::mutator_lock_)
     REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !*backtrace_lock_,
              !Roles::uninterruptible_) {
    return AllocObjectWithAllocator<kInstrumented, true>(
       self, klass, num_bytes, GetCurrentAllocator(), pre_fence_visitor);
    }

    在这个方法的实现中,会首先通过Heap::TryToAllocate尝试进行内存的分配。在Heap::TryToAllocate方法,会根据AllocatorType,选择不同的Space进行内存的分配,下面是部分代码片段:

    // heap-inl.h
    
    case kAllocatorTypeRosAlloc: {
      if (kInstrumented && UNLIKELY(is_running_on_memory_tool_)) {
        ...
      } else {
        DCHECK(!is_running_on_memory_tool_);
        size_t max_bytes_tl_bulk_allocated =
            rosalloc_space_->MaxBytesBulkAllocatedForNonvirtual(alloc_size);
        if (UNLIKELY(IsOutOfMemoryOnAllocation<kGrow>(allocator_type,
                                                      max_bytes_tl_bulk_allocated))) {
          return nullptr;
        }
        if (!kInstrumented) {
          DCHECK(!rosalloc_space_->CanAllocThreadLocal(self, alloc_size));
        }
        ret = rosalloc_space_->AllocNonvirtual(self, alloc_size, bytes_allocated, usable_size,
                                               bytes_tl_bulk_allocated);
      }
      break;
    }
    case kAllocatorTypeDlMalloc: {
      if (kInstrumented && UNLIKELY(is_running_on_memory_tool_)) {
        // If running on valgrind, we should be using the instrumented path.
        ret = dlmalloc_space_->Alloc(self, alloc_size, bytes_allocated, usable_size,
                                     bytes_tl_bulk_allocated);
      } else {
        DCHECK(!is_running_on_memory_tool_);
        ret = dlmalloc_space_->AllocNonvirtual(self, alloc_size, bytes_allocated, usable_size,
                                               bytes_tl_bulk_allocated);
      }
      break;
    }
    ...
    case kAllocatorTypeLOS: {
      ret = large_object_space_->Alloc(self, alloc_size, bytes_allocated, usable_size,
                                       bytes_tl_bulk_allocated);
      // Note that the bump pointer spaces aren't necessarily next to
      // the other continuous spaces like the non-moving alloc space or
      // the zygote space.
      DCHECK(ret == nullptr || large_object_space_->Contains(ret));
      break;
    }
    case kAllocatorTypeTLAB: {
      ...
    }
    case kAllocatorTypeRegion: {
      DCHECK(region_space_ != nullptr);
      alloc_size = RoundUp(alloc_size, space::RegionSpace::kAlignment);
      ret = region_space_->AllocNonvirtual<false>(alloc_size, bytes_allocated, usable_size,
                                                  bytes_tl_bulk_allocated);
      break;
    }
    case kAllocatorTypeRegionTLAB: {
      ...
      // The allocation can't fail.
      ret = self->AllocTlab(alloc_size);
      DCHECK(ret != nullptr);
      *bytes_allocated = alloc_size;
      *usable_size = alloc_size;
      break;
    }

    AllocatorType的类型有如下一些:

    enum AllocatorType {
      kAllocatorTypeBumpPointer,  // Use BumpPointer allocator, has entrypoints.
      kAllocatorTypeTLAB,  // Use TLAB allocator, has entrypoints.
      kAllocatorTypeRosAlloc,  // Use RosAlloc allocator, has entrypoints.
      kAllocatorTypeDlMalloc,  // Use dlmalloc allocator, has entrypoints.
      kAllocatorTypeNonMoving,  // Special allocator for non moving objects, doesn't have entrypoints.
      kAllocatorTypeLOS,  // Large object space, also doesn't have entrypoints.
      kAllocatorTypeRegion,
      kAllocatorTypeRegionTLAB,
    };

    如果Heap::TryToAllocate失败(返回nullptr),会尝试进行垃圾回收,然后再进行内存的分配:

    obj = TryToAllocate<kInstrumented, false>(self, allocator, byte_count, &bytes_allocated,
                                                  &usable_size, &bytes_tl_bulk_allocated);
        if (UNLIKELY(obj == nullptr)) {
          obj = AllocateInternalWithGc(self,
                                       allocator,
                                       kInstrumented,
                                       byte_count,
                                       &bytes_allocated,
                                       &usable_size,
                                       &bytes_tl_bulk_allocated, &klass);
    ...

    AllocateInternalWithGc方法中,会先尝试进行内存回收,然后再进行内存的分配。

    垃圾回收

    在Dalvik虚拟机上,垃圾回收会造成两次停顿,第一次需要3~4毫秒,第二次需要5~6毫秒,虽然两次停顿累计也只有约10毫秒的时间,但是即便这样也是不能接受的。因为对于60FPS的渲染要求来说,每秒钟需要更新60次画面,那么留给每一帧的时间最多也就只有16毫秒。如果垃圾回收就造成的10毫秒的停顿,那么就必然造成丢帧卡顿的现象。

    因此垃圾回收机制是ART虚拟机重点改进的内容之一。

    ART虚拟机垃圾回收概述

    ART 有多个不同的 GC 方案,这些方案包括运行不同垃圾回收器。默认方案是 CMS(Concurrent Mark Sweep,并发标记清除)方案,主要使用粘性(sticky)CMS 和部分(partial)CMS。粘性CMS是ART的不移动(non-moving )分代垃圾回收器。它仅扫描堆中自上次 GC 后修改的部分,并且只能回收自上次GC后分配的对象。除CMS方案外,当应用将进程状态更改为察觉不到卡顿的进程状态(例如,后台或缓存)时,ART 将执行堆压缩。

    除了新的垃圾回收器之外,ART 还引入了一种基于位图的新内存分配程序,称为 RosAlloc(插槽运行分配器)。此新分配器具有分片锁,当分配规模较小时可添加线程的本地缓冲区,因而性能优于 DlMalloc。

    与 Dalvik 相比,ART CMS垃圾回收计划在很多方面都有一定的改善:

    • 与Dalvik相比,暂停次数2次减少到1次。Dalvik的第一次暂停主要是为了进行根标记。而在ART中,标记过程是并发进行的,它让线程标记自己的根,然后马上就恢复运行。
    • 与Dalvik类似,ART GC在清除过程开始之前也会暂停1次。两者在这方面的主要差异在于:在此暂停期间,某些Dalvik的处理阶段在ART中以并发的方式进行。这些阶段包括 java.lang.ref.Reference处理、系统弱引用清除(例如,jni全局弱引用等)、重新标记非线程根和卡片预清理。在ART暂停期间仍进行的阶段包括扫描脏卡片以及重新标记线程根,这些操作有助于缩短暂停时间。
    • 相对于Dalvik,ART GC改进的最后一个方面是粘性 CMS回收器增加了GC吞吐量。不同于普通的分代GC,粘性 CMS 不会移动。年轻对象被保存在一个分配堆栈(基本上是 java.lang. Object 数组)中,而非为其设置一个专用区域。这样可以避免移动所需的对象以维持低暂停次数,但缺点是容易在堆栈中加入大量复杂对象图像而使堆栈变长。

    ART GC与Dalvik的另一个主要区别在于 ART GC引入了移动垃圾回收器。使用移动 GC的目的在于通过堆压缩来减少后台应用使用的内存。目前,触发堆压缩的事件是 ActivityManager 进程状态的改变(参见第2章第3节)。当应用转到后台运行时,它会通知ART已进入不再“感知”卡顿的进程状态。此时ART会进行一些操作(例如,压缩和监视器压缩),从而导致应用线程长时间暂停。目前正在使用的两个移动GC是同构空间压缩(Homogeneous Space Compact)和半空间(Semispace Compact)压缩。

    • 半空间压缩将对象在两个紧密排列的碰撞指针空间之间进行移动。这种移动 GC 适用于小内存设备,因为它可以比同构空间压缩稍微多节省一点内存。额外节省出的空间主要来自紧密排列的对象,这样可以避免 RosAlloc/DlMalloc 分配器占用开销。由于 CMS 仍在前台使用,且不能从碰撞指针空间中进行收集,因此当应用在前台使用时,半空间还要再进行一次转换。这种情况并不理想,因为它可能引起较长时间的暂停。
    • 同构空间压缩通过将对象从一个 RosAlloc 空间复制到另一个 RosAlloc 空间来实现。这有助于通过减少堆碎片来减少内存使用量。这是目前非低内存设备的默认压缩模式。相比半空间压缩,同构空间压缩的主要优势在于应用从后台切换到前台时无需进行堆转换。

    GC 验证和性能选项

    你可以采用多种方法来更改ART使用的GC计划。更改前台GC计划的主要方法是更改 dalvik.vm.gctype 属性或传递 -Xgc: 选项。你可以通过以逗号分隔的格式传递多个 GC 选项。

    为了导出可用 -Xgc 设置的完整列表,可以键入 adb shell dalvikvm -help 来输出各种运行时命令行选项。

    以下是将 GC 更改为半空间并打开 GC 前堆验证的一个示例: adb shell setprop dalvik.vm.gctype SS,preverify

    • CMS 这也是默认值,指定并发标记清除 GC 计划。该计划包括运行粘性分代 CMS、部分 CMS 和完整 CMS。该计划的分配器是适用于可移动对象的 RosAlloc 和适用于不可移动对象的 DlMalloc。
    • SS 指定半空间 GC 计划。该计划有两个适用于可移动对象的半空间和一个适用于不可移动对象的 DlMalloc 空间。可移动对象分配器默认设置为使用原子操作的共享碰撞指针分配器。但是,如果 -XX:UseTLAB 标记也被传入,则分配器使用线程局部碰撞指针分配。
    • GSS 指定分代半空间计划。该计划与半空间计划非常相似,但区别在于其会将存留期较长的对象提升到大型 RosAlloc 空间中。这样就可明显减少典型用例中需复制的对象。

    内部实现

    在ART虚拟机中,很多场景都会触发垃圾回收的执行。ART代码中通过GcCause这个枚举进行描述,包括下面这些事件:

    常量说明
    kGcCauseForAlloc内存分配失败
    kGcCauseBackground后台进程的垃圾回收,为了确保内存的充足
    kGcCauseExplicit明确的System.gc()调用
    kGcCauseForNativeAlloc由于native的内存分配
    kGcCauseCollectorTransition垃圾收集器发生了切换
    kGcCauseHomogeneousSpaceCompact当前景和后台收集器都是CMS时,发生了后台切换
    kGcCauseClassLinkerClassLinker导致

    另外,垃圾回收策略有三种类型:

    • Sticky 仅仅释放上次GC之后创建的对象
    • Partial 仅仅对应用程序的堆进行垃圾回收,但是不处理Zygote的堆
    • Full 会对应用程序和Zygote的堆都会进行垃圾回收

    这里Sticky类型的垃圾回收便是基于“分代”的垃圾回收思想,根据IBM的一项研究表明,新生代中的对象有98%是生命周期很短的。所以将新创建的对象单独归为一类来进行GC是一种很高效的做法。

    真正负责垃圾回收的逻辑是下面这个方法:

    // heap.cc
    
    collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type,
                                                   GcCause gc_cause,
                                                   bool clear_soft_references)

    CollectGarbageInternal方法中,会根据当前的GC类型和原因,选择合适的垃圾回收器,然后执行垃圾回收。

    ART虚拟机中内置了多个垃圾回收器,包括下面这些:

    art_gc_alg.png

    ART虚拟机中的垃圾回收器

    这里的Compact类型的垃圾回收器便是前面提到“标记-压缩”算法。这种类型的垃圾回收器,会在将对象清理之后,将最终还在使用的内存空间移动到一起,这样可以既可以减少堆中的碎片,也节省了堆空间。但是由于这种垃圾回收器需要对内存进行移动,所以耗时较多,因此这种垃圾回收器适合于切换到后台的应用。

    前面我们提到过:垃圾收集器会在Heap的构造函数中被创建,然后添加到garbage_collectors_列表中。

    尽管各种垃圾回收器算法不一定,但它们都包含相同的垃圾回收步骤,垃圾回收器的回收过程主要包括下面四个步骤:

    gc_phase.png

    垃圾回收的四个阶段

    所以,想要深入明白每个垃圾回收器的算法细节,只要按照这个逻辑来理解即可。

    JIT的回归

    前面我们提到:在Android 5.0上,系统在安装APK时会直接将dex文件中的代码编译成机器码。我们应该知道,编译的过程是比较耗时的。因此,用过Android 5.0的用户应该都会感觉到,在这个版本上安装应用程序明显比之前要慢了很多。

    编译一个应用程序已经比较耗时,但如果系统中所有的应用都要重新编译一遍,那等待时间将是难以忍受的。但不幸的事,这样的事情却刚好发生了,相信用过Android 5.0的Nexus用户都看到过这样一个画面:

    android-phone-system-update.jpg

    Android 5.0的启动画面

    之所以发生这个问题,是因为:

    • 应用程序编译生成的OAT文件会引用Framework中的代码。一旦系统发生升级,Framework中的实现发生变化,就需要重新修正所有应用程序的OAT文件,使得它们的引用是正确的,这就需要重新编译所有的应用
    • 出于系统的安全性考虑,自2015年8月开始,Nexus设备每个月都会收到一次安全更新

    要让用户每个月都要忍受一次这么长的等待时间,显然是不能接受的。

    由此我们看到,单纯的AOT编译存在如下两个问题:

    • 应用安装时间过长
    • 系统升级时,所有应用都需要重新编译

    其实这里还有另外一个问题,我们也应该能想到:编译生成的Oat文件中,既包含了原先的Dex文件,又包含了编译后的机器代码。而实际上,对于用户来说,并非会用到应用程序中的所有功能,因此很多时候编译生成的机器码是一直用不到的。一份数据存在两份结果(尽管它们的格式是不一样的)显然是一种存储空间的浪费。

    因此,为了解决上面提到的这些问题,在 Android 7.0 中,Google又为Android添加了即时 (JIT) 编译器。JIT和AOT的配合,是取两者之长,避两者之短:在APK安装时,并不是一次性将所有代码全部编译成机器码。而是在实际运行过程中,对代码进行分析,将热点代码编译成机器码,让它可以在应用运行时持续提升 Android 应用的性能。

    JIT编译器补充了ART当前的预先(AOT)编译器的功能,有助于提高运行时性能,节省存储空间,以及加快应用及系统更新速度。相较于 AOT编译器,JIT编译器的优势也更为明显,因为它不会在应用自动更新期间或重新编译应用(在无线下载 (OTA) 更新期间)时拖慢系统速度。

    尽管JIT和AOT使用相同的编译器,它们所进行的一系列优化也较为相似,但它们生成的代码可能会有所不同。JIT会利用运行时类型信息,可以更高效地进行内联,并可让堆栈替换 (On Stack Replacement) 编译成为可能,而这一切都会使其生成的代码略有不同。

    JIT的运行流程如下:

    jit-architecture.png

    JIT的运行流程

    1. 用户运行应用,而这随后就会触发 ART 加载 .dex 文件。

      • 如果有 .oat 文件(即 .dex 文件的 AOT 二进制文件),则 ART 会直接使用该文件。虽然 .oat 文件会定期生成,但文件中不一定会包含经过编译的代码(即 AOT 二进制文件)。
      • 如果没有 .oat 文件,则 ART 会通过 JIT 或解释器执行 .dex 文件。如果有 .oat 文件,ART 将一律使用这类文件。否则,它将在内存中使用并解压 APK 文件,从而得到 .dex 文件,但是这会导致消耗大量内存(相当于 dex 文件的大小)。
    2. 针对任何未根据speed编译过滤器编译(见下文)的应用启用JIT(也就是说,要尽可能多地编译应用中的代码)。
    3. 将 JIT 配置文件数据转存到只限应用访问的系统目录内的文件中。
    4. AOT 编译 (dex2oat) 守护进程通过解析该文件来推进其编译。

    控制JIT日志记录

    要开启 JIT 日志记录,请运行以下命令:

    adb root
    adb shell stop
    adb shell setprop dalvik.vm.extra-opts -verbose:jit
    adb shell start

    要停用 JIT,请运行以下命令:

    adb root
    adb shell stop
    adb shell setprop dalvik.vm.usejit false
    adb shell start

    ART虚拟机的演进与配置

    从Android 7.0开始,ART组合使用了AOT和JIT。并且这两者是可以单独配置的。例如,在Pixel设备上,相应的配置如下:

    1. 最初在安装应用程序的时候不执行任何AOT编译。应用程序运行的前几次都将使用解释模式,并且经常执行的方法将被JIT编译。
    2. 当设备处于空闲状态并正在充电时,编译守护进程会根据第一次运行期间生成的Profile文件对常用代码运行AOT编译。
    3. 应用程序的下一次重新启动将使用Profile文件引导的代码,并避免在运行时为已编译的方法进行JIT编译。在新运行期间得到JIT编译的方法将被添加到Profile文件中,然后被编译守护进程使用。

    在应用程序安装时,APK文件会传递给dex2oat工具,该工具会为根据APK文件生成一个或多个编译产物,这些产物文件名和扩展名可能会在不同版本之间发生变化,但从Android 8.0版本开始,生成的文件是:

    • .vdex:包含APK的未压缩Dex代码,以及一些额外的元数据用来加速验证。
    • .odex:包含APK中方法的AOT编译代码。(注意,虽然Dalvik虚拟机时代也会生成odex文件,但和这里的odex文件仅仅是后缀一样,文件内容已经完全不同了)
    • .art(可选):包含APK中列出的一些字符串和类的ART内部表示,用于加速应用程序的启动。

    ART虚拟机在演进过程中,提供了很多的配置参数供系统调优,关于这部分内容,请参见这里:AOSP:配置 ART

    参考资料与推荐读物

    展开全文
  • ART 虚拟机 — Interpreter 模式

    千次阅读 2018-06-18 20:43:26
    ART 虚拟机执行 Java 方法主要有两种模式:quick code 模式和 Interpreter 模式; quick code 模式:执行 arm 汇编指令 Interpreter 模式:由解释器解释执行 Dalvik 字节码 本篇文章就来讲一下,...
  • 相信大家应该都知道了,Android 4.4中谷歌为开发者提供了两种编译模式,一种是默认的Dalvik模式,而另外一种则是 ART模式。现在最新的消息显示,谷歌正在对Android系统进行调整,简单点说就是ART已经取代Dalvik成为...
  • Android使用Dalvik虚拟机的那个时代,安卓设备用于存储应用程序的空间往往很小,所以节省存储空间是最重要的,而到了Android使用ART虚拟机的时代,安卓设备用于存储应用程序的空间已经足够了,用户们可以追求更高...
  • Android Dex vs ART 虚拟机运行效率提升

    千次阅读 2014-07-14 18:35:02
    Android 4.4提供了一种与Dalvik截然不同的运行环境ART(Android runtime)支持,ART源于google收购的Flexycore的公司。ART模式与Dalvik模式最大的不同在于,启用ART模式后,系统在安装应用的时候会进行一次预编译,将...
  • 2、Android4.4中引入了ART虚拟机,但默认的Android运行时依然是Dalvik虚拟机。 3、Android5.0开始的Android运行时默认是ART虚拟机。它使用提前编译。 7-2、Dalvik虚拟机 1、Dalvik和标准Java虚拟机(JVM)首要差...
  • Android art模式解析

    千次阅读 2015-12-14 19:12:29
    Android art模式解析 本文主要针对android系统art模式下面从安装apk到运行apk的一个过程,主要有一下几个...Art是和Dalvik类似的虚拟机,所不同的是Dalvik虚拟机执行的是dex字节码,Art虚拟机执行的是本地机器码,这也
  • ART虚拟机最大的特点就是,将代码优化的过程从Davlik的JIT(Just In Time)模式转换成了AOT(Ahead Of Time)模式。也就是说,在程序正式执行之前就完成了优化和编译的工作。而编译的时机时在程序安装的时候,由dex2
  • 因此我们也称Java虚拟机是基于栈的,这点不同于Android虚拟机Android虚拟机是基于寄存器的。 基于栈的指令集最主要的优点是可移植性强,主要的缺点是执行速度相对会慢些;而由于寄存器由硬件直接提供,所以基于...
  • art虚拟机

    2020-08-20 15:58:35
    ART是在Android 4.0引入并在Android 5.0中设为默认解决方案的主要特性之一。ART取代了Dalvik,但是前者与...但是在Android N开发者预览版包含了一个混合模式的运行时。应用在安装时不做编译,而是解释字节码,所以可以
  • 了解一下,Android 10中的ART虚拟机(I)

    万次阅读 2020-01-13 21:17:00
    缘起从今天开始,对Android 10中的ART进行“了解一下”之旅。我本来以为一篇就能拿下,但谁想谷歌从8.0开始对ART进行了更细致和难度更高的改进。粗看了几遍代码,我觉得谷歌不管怎...
  • Android4.4版本以前是Dalvik虚拟机,4.4版本开始引入ART虚拟机Android Runtime)。在4.4版本上,两种运行时环境共存,可以相互切换,但是在5.0版本以后,Dalvik虚拟机则被彻底的丢弃,全部采用ART。 总之,一句话...
  • JAVA虚拟机、Dalvik虚拟机和ART虚拟机简要对比

    万次阅读 多人点赞 2015-12-31 11:43:14
    简单介绍JAVA虚拟机,安卓Dalvik虚拟机和ART虚拟机,并做简单对比。
  • Android L预览版的发布让很多机友把目光聚焦在了ART...首先我们来看一下更新迭代后Android虚拟机 ... AndroidL预览版的发布让很多机友把目光聚焦在了ARTAndroid Runtime)虚拟机上,一夜之间所有的用户都视...
  • Android使用Dalvik虚拟机的那个时代,安卓设备用于存储应用程序的空间往往很小,所以节省存储空间是最重要的,而到了Android使用ART虚拟机的时代,安卓设备用于存储应用程序的空间已经足够了,用户们可以追求更高...
  • ART虚拟机oat文件

    2018-12-07 19:03:09
    5.0以上android强制用art虚拟机 oat文件在安装过程中dex2oat系统工具将apk所有dex文件转成oat文件放在/data/dalvik-cache/arm目录下后缀还是以dex/odex结尾
  • 本系列文章记录对ART虚拟机学习过程中的一些资料记录,及个人对虚拟机的一些理解。
  • Java方法在art虚拟机中的执行

    千次阅读 2018-12-19 09:36:15
    ART 虚拟机执行 Java 方法主要有两种模式:quick code 模式和 Interpreter 模式 quick code 模式:执行 arm 汇编指令 Interpreter 模式:由解释器解释执行 Dalvik 字节码 在之前的文章 ART 虚拟机 — ...
  • Android逆向 Android平台虚拟机 一 Dalvik:是Google开发运行在Android平台的Java虚拟机Android程序编译后会生成dex文件。Dalvik虚拟机下运行Java时,要将字节码通过即时编译器(just in time ,JIT)转换...
  • art虚拟机启动过程分析

    千次阅读 2016-12-03 16:01:36
    android art jvm
  • Android Dalvik虚拟机(尽管现在被art取代,但不代表它一无是处,了解dalvik有助于理解art)在Android 4.4之前,Dalvik一直是Android中默认的虚拟机,后面推出了ART运行环境机制,逐步完全取代了Dalvik。Dalvik 和...
  • Android了解虚拟机

    2017-04-03 14:30:26
    1.Android Runtime (ART) 是运行 Android 5.0(API 级别 21)及更高版本的设备的默认运行时。 此运行时提供了多种可改善 Android 平台和应用的性能和流畅度的功能。 2.可以通过调用 System.getProperty(“java.vm....
  • 了解一下,Android 10中的ART虚拟机(4)

    千次阅读 2020-04-12 17:27:58
    缘起今天及后续几篇关于ART的知识,我将从读书笔记的角度来系统的学习《Advanced Design and Implementation of Virtual Machines》。这...
  • ART虚拟机中的DEX文件脱壳技术

    千次阅读 2018-09-07 18:45:18
    摘要: 在对现有的DEX加固技术和脱壳技术进行系统学习和研究的基础上,提出和实现了一种基于Android ART虚拟机(VM)的DEX脱壳方案。该方案能够从加固的Android应用中还原出原始DEX文件,其核心思想是将静态插桩和...
  • art虚拟机介绍

    2016-04-14 14:26:54
    Android 4.4提供了一种与Dalvik截然不同的运行环境ART(Android runtime)支持,ART源于google收购的Flexycore的公司。ART模式与Dalvik模式最大的不同在于,启用ART模式后,系统在安装应用的时候会进行一次预编译,将...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,649
精华内容 2,259
热门标签
关键字:

androidart虚拟机模式