[关闭]
@946898963 2020-07-15T09:48:44.000000Z 字数 8675 阅读 1777

Android虚拟机的一些知识

Android虚拟机


JIT&AOT

JIT

传统JIT技术

JIT编译器,英文写作Just-In-Time Compiler,中文意思是即时编译器。JIT是一种提高程序运行效率的方法。

通常,程序有两种运行方式:静态编译与动态解释。静态编译的程序在执行前全部被翻译为机器码,而解释执行的则是一句一句边运行边翻译。

当你写好一个Java程序后,源语言的语句将由Java编译器编译成字节码,而不是编译成与某个特定的处理器硬件平台对应的指令代码(比如,Intel的Pentium微处理器或IBM的System/390处理器)。字节码是可以发送给任何平台并且能在那个平台上运行的独立于平台的代码。在Java编程语言和环境中,即时编译器(JIT compiler,just-in-time compiler)是一个把Java的字节码(包括需要被解释的指令的程序)转换成可以直接发送给处理器的机器码的程序。

此处输入图片的描述

在部分商用虚拟机中(如HotSpot),Java程序最初是通过解释器(Interpreter)进行解释执行的,,即对字节码逐条解释执行,这种方式的执行速度相对会比较慢,尤其当某个方法或代码块运行的特别频繁时,这种方式的执行效率就显得很低。于是后来在虚拟机中引入了 JIT 编译器(即时编译器),当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,下文统称JIT编译器)

即时编译器并不是虚拟机必须的部分,Java虚拟机规范并没有规定Java虚拟机内必须要有即时编译器存在,更没有限定或指导即时编译器应该如何去实现。但是,即时编译器编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机优秀与否的最关键的指标之一,它也是虚拟机中最核心且最能体现虚拟机技术水平的部分。

现在主流的商用虚拟机(如Sun HotSpot、IBM J9)中几乎都同时包含解释器和编译器(三大商用虚拟机之一的 JRockit 是个例外,它内部没有解释器,因此会有启动相应时间长之类的缺点,但它主要是面向服务端的应用,这类应用一般不会重点关注启动时间)。二者各有优势:当程序需要迅速启动和执行时,解释器可以首先发挥作用,省去编译的时间,立即执行;当程序运行后,随着时间的推移,编译器逐渐会返回作用,把越来越多的代码编译成本地代码后,可以获取更高的执行效率。解释执行可以节约内存,而编译执行可以提升效率。

HotSpot 虚拟机中内置了两个JIT编译器:Client Complier 和 Server Complier,分别用在客户端和服务端,目前主流的 HotSpot 虚拟机中默认是采用解释器与其中一个编译器直接配合的方式工作。

运行过程中会被即时编译器编译的“热点代码”有两类:

两种情况,编译器都是以整个方法作为编译对象,这种编译也是虚拟机中标准的编译方式。要知道一段代码或方法是不是热点代码,是不是需要触发即时编译,需要进行 Hot Spot Detection(热点探测)。目前主要的热点 判定方式有以下两种:

在 HotSpot 虚拟机中使用的是第二种——基于计数器的热点探测方法,因此它为每个方法准备了两个计数器:方法调用计数器和回边计数器。

方法调用计数器用来统计方法调用的次数,在默认设置下,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间内方法被调用的次数。

回边计数器用于统计一个方法中循环体代码执行的次数(准确地说,应该是回边的次数,因为并非所有的循环都是回边),在字节码中遇到控制流向后跳转的指令就称为“回边”。

在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阀值,当计数器的值超过了阀值,就会触发JIT编译。触发了JIT编译后,在默认设置下,执行引擎并不会同步等待编译请求完成,而是继续进入解释器按照解释方式执行字节码,直到提交的请求被编译器编译完成为止(编译工作在后台线程中进行)。当编译工作完成后,下一次调用该方法或代码时,就会使用已编译的版本。

由于方法计数器触发即时编译的过程与回边计数器触发即时编译的过程类似,因此这里仅给出方法调用计数器触发即时编译的流程:

javac字节码编译器与虚拟机内的JIT编译器的执行过程合起来其实就等同于一个传统的编译器所执行的编译过程。

此处输入图片的描述

Android上的JIT技术

Android中的JIT是在Android 2.2版本提出的,目的是为了提高android的运行速度,一直存活到4.4版本,因为在4.4之后的ROM中,就不存在Dalvik虚拟机了。我们使用Java开发android,在编译打包APK文件时,会经过以下流程:

  1. Java编译器将应用中所有Java文件编译为class文件
  2. dx工具将应用编译输出的类文件转换为Dalvik字节码,即dex文件

DVM负责解释dex文件为机器码,如果我们不做处理的话,每次执行代码,都需要Dalvik将java代码由解释器(Interpreter)将每个java指令转译为微处理器指令,并根据转译后的指令先后次序依序执行,一条java指令可能对应多条微处理器指令,这样效率不高。为了解决这个问题,Google在2.2版本添加了JIT编译器,当App运行时,每当遇到一个新类,JIT编译器就会对这个类进行编译,经过编译后的代码,会被优化成相当精简的原生型指令码(即native code),这样在下次执行到相同逻辑的时候,速度就会更快。但是使用JIT也不一定加快执行速度,如果大部分代码的执行次数很少,那么编译花费的时间不一定少于执行dex的时间。Google当然也知道这一点,所以JIT不对所有dex代码进行编译,而是只编译执行次数较多的dex为本地机器码。

Java解释执行相关参考链接:

JAVA的解释执行

Java的解释执行是先全部解释完后再执行还是边解释边执行?

Java JIT技术相关参考链接:

JIL 编译与 AOT 编译

javac 编译与 JIT 编译 (流程图特别留意)

为什么 JVM 不用 JIT 全程编译? - RednaxelaFX的回答 - 知乎

Java中的JIT机制对运行速度的优化

凭啥Java的运行环境称虚拟机,Python的只能称解释器

JVM、Java编译器和Java解释器

JIT与JVM的三种执行模式:解释模式、编译模式、混合模式

浅谈对JIT编译器的理解。

关于java的JIT知识

java即时编译器JIT之简单介绍

如何通俗易懂地介绍「即时编译」(JIT),它的优点和缺点是什么? - supky的回答 - 知乎

浅析 JIT 即时编译技术

JVM杂谈之JIT

如何理解JIT编译技术?

JIT编译器

深入浅出 JIT 编译器

什么是JIT?

什么是即时(JIT)编译器呢?

JIT是把二进制代码保存下来还是每次使用的时候重新编译?

AOT

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

对比JIT和AOT,各自有什么优点与缺点? - Aaron luo的回答 - 知乎

dex&odex&oat

dex

dex(Dalvik Executable),本质上java文件编译后都是字节码,只不过JVM运行的是.class字节码,而DVM运行的是.dex字节码,sdk\build-tools\25.0.2\dx工具负责将Java字节码.class文件转换为Dalvik字节码.dex,dx工具对Java类文件重新排列,消除在类文件中出现的所有冗余信息,避免虚拟机在初始化时出现反复的文件加载与解析过程。一般情况下,Java类文件中包含多个不同的方法签名,如果其他的类文件引用该类文件中的方法,方法签名也会被复制到其类文件中,也就是说,多个不同的类会同时包含相同的方法签名,同样地,大量的字符串常量在多个类文件中也被重复使用。这些冗余信息会直接增加文件的体积,同时也会严重影响虚拟机解析文件的效率。消除其中的冗余信息,重新组合形成一个常量池,所有的类文件共享同一个常量池,由于dx工具对常量池的压缩,使得相同的字符串,常量在DEX文件中只出现一次,从而减小了文件的体积,同时也提高了类的查找速度,此外,dex格式文件增加了新的操作码支持,文件结构也相对简洁,使用等长的指令来提高解析速度。

odex

odex(Optimized dex),即优化的dex,主要是为了提高DVM的运行速度,在编译打包APK时,Java类会被编译成一个或者多个字节码文件(.class),通过dx工具CLASS文件转换成一个DEX(Dalvik Executable)文件。 通常情况下,我们看到的Android应用程序实际上是一个以.apk为后缀名的压缩文件。我们可以通过压缩工具对apk进行解压,解压出来的内容中有一个名为classes.dex的文件。那么我们首次开机的时候系统需要将其从apk中解压出来保存在data/app目录中。 如果当前运行在Dalvik虚拟机下,Dalvik会对classes.dex进行一次“翻译”,“翻译”的过程也就是守护进程installd的函数dexopt来对dex字节码进行优化,实际上也就是由dex文件生成odex文件,最终odex文件被保存在手机的VM缓存目录data/dalvik-cache下(注意!这里所生成的odex文件依旧是以dex为后缀名,格式如:system@priv-app@Settings@Settings.apk@classes.dex)。如果当前运行于ART模式下, ART同样会在首次进入系统的时候调用/system/bin/dexopt(此处应该是dex2oat工具吧)工具来将dex字节码翻译成本地机器码,保存在data/dalvik-cache下。 那么这里需要注意的是,无论是对dex字节码进行优化,还是将dex字节码翻译成本地机器码,最终得到的结果都是保存在相同名称的一个odex文件里面的,但是前者对应的是一个.dex文件(表示这是一个优化过的dex),后者对应的是一个.oat文件。通过这种方式,原来任何通过绝对路径引用了该odex文件的代码就都不需要修改了。 由于在系统首次启动时会对应用进行安装,那么在预置APK比较多的情况下,将会大大增加系统首次启动的时间。

从前面的描述可知,既然无论是DVM还是ART,对DEX的优化结果都是保存在一个相同名称的odex文件,那么如果我们把这两个过程在ROM编译的时候预处理提取Odex文件将会大大优化系统首次启动的时间。具体做法则是在device目录下的/device/huawei/angler/BoardConfig.mk中定义WITH_DEXPREOPT := true,打开这个宏之后,无论是有源码还是无源码的预置apk预编译时都会提取odex文件,不过这里需要注意的是打开WITH_DEXPREOPT 宏之后,预编译时提取Odex会增加一定的空间,预置太多apk,会导致system.img 过大,而编译不过。遇到这种情况可以通过删除apk中的dex文件、调大system.img的大小限制,或在预编译时跳过一些apk的odex提取。

DVM如果采用了分包技术的话,在安装阶段,会对miandex执行dexopt操作,在应用第一次启动的时候,会对second dex进行dexopt操作,而dexopt操作是比较耗时的,所以会导致黑屏甚至ANR异常出现,同时也可能会出现由于类找不到而导致的崩溃。

oat

oat文件是ART的核心,是通过/system/bin/dex2oat 工具生成的,实际上是一个自定义的elf文件,里面包含的都是本地机器指令,通过AOT生成的文件,在系统中的表现形式有OAT、ART、ODEX,其中大部分apk在执行AOT后生成的都是odex文件。但是由dex2oat工具生成的oat文件包含有两个特殊的段oatdata和oatexec,前者包含有用来生成本地机器指令的dex文件内容,后者包含有生成的本地机器指令,进而就可以直接运行。其是通过PMS –> installd –> dex2oat的流程生成的,可以在预编译的时候,也可以在开机apk扫描的过程中或者apk安装过程中生成。

Art VM在安装阶段就会合并所有的dex,dexoat整体只触发一次。所以在使用Multidex的时候,应用启动的时候,不会出现黑屏异常和由于类找不到而触发的崩溃。

关于Dalvik虚拟机

Dalvik虚拟机是Google按照JVM虚拟机规范定制的虚拟机,它更符合移动设备的环境要求。

与标准虚拟机不同

DVM执行的是.dex文件,JVM执行的是.class文件。在Java程序中,Java类会被编译成一个或多个class文件,然后打包到jar文件中,接着Java虚拟机会从相应的class文件和jar文件中获取对应的字节码。Android应用虽然也使用Java语言,但是在编译成class文件后,还会通过DEX工具将所有的class文件转换成一个dex文件,Dalvik虚拟机再从中读取指令和数据。dex文件去除了.class文件中的冗余的代码,除了减少了整体的文件尺寸和I/O操作次数,也提高了类的查找速度。

此处输入图片的描述

关于dex和class的更详细的差异,建议阅读:理解Android虚拟机体系结构&&关于Dalvik、ART、DEX、ODEX、JIT、AOT、OAT

Dalvik虚拟机是基于寄存器的,而JVM规范是基于栈的,所以速度方面会有优势。寄存器是处理器的一部分,栈属于内存,所以寄存器更快些。大多数Java虚拟机都是基于栈的结构(详情请参考:理解Java虚拟机体系结构),而Dalvik虚拟机则是基于寄存器。基于栈的指令很紧凑,例如,Java虚拟机使用的指令只占一个字节,因而称为字节码。基于寄存器的指令由于需要指定源地址和目标地址,因此需要占用更多的指令空间。Dalvik虚拟机的某些指令需要占用两个字节。基于栈和基于寄存器的指令集各有优劣,一般而言,执行同样的功能,前者需要更多的指令(主要是load和store指令),而后者需要更多的指令空间。需要更多指令意味着要多占用CPU时间,而需要更多指令空间意味着数据缓冲(d-cache)更易失效。更多讨论,虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩给出了非常详细的参考。

寄存器是CPU上面的一块存储空间,栈是内存上面的一段连续的存储空间,所以CPU直接访问自己上面的一块空间的数据的效率肯定要大于访问内存上面的数据。基于栈架构的程序在运行时虚拟机需要频繁的从栈上读取或写入数据,这个过程需要更多的指令分派与内存访问次数,会耗费不少CPU时间,对于像手机设备资源有限的设备来说,这是相当大的一笔开销。DVM基于寄存器架构。数据的访问通过寄存器间直接传递,这样的访问方式比基于栈方式要快很多。

DVM:允许运行多个虚拟机实例,每一个应用启动都运行一个单独的虚拟机,并且运行在一个独立的进程中。

JVM:只能运行一个实例,也就是所有应用都运行在同一个JVM中。

理解Android虚拟机体系结构

Android虚拟机和Java虚拟机之间的区别

JAVA虚拟机与Android虚拟机的区别

关于Dalvik、ART、DEX、ODEX、JIT、AOT、OAT

关于ART虚拟机

ART即Android Runtime,是在Dalvik的基础上做了一些优化之后推出的新的Android虚拟机。在Dalvik下,应用每次运行的时候,虽然采用了即时编译器(JIT, just in time)技术提升了效率,但是代码总体上仍然是解释执行的,这拖慢了应用的运行效率,而在ART环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。这个过程叫做预编译(AOT, Ahead-Of-Time)。这样的话,应用的启动(首次)和执行都会变得更加快速。

ART虚拟机执行的本地机器码:

.java –> java bytecode(.class) –> dalvik bytecode(.dex) –> optimized android runtime machine code(.oat)

Android 4.4之前是DVM,在4.4版本上,两种运行时环境共存,可以相互切换,但是在5.0+,Dalvik虚拟机则被彻底的丢弃,全部采用ART。

ART虚拟机与Dalvik虚拟机的不同

ART虚拟机在应用程序安装时就会把字节码通过dex2oat工具直接转成机器码储存,这个过程叫做AOT(Ahead-Of-Time)。而Dalvik是在每次启动应用程序时,通过传统的JIT(JUST IN TIME)模式将字节码转成机器码。显然,这样速度会慢不少。当然,ART虚拟机的占用内存也会更大些。

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

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

原因就是因为两者执行可执行文件的方式是不同的,一个是解释执行dex文件,另一个是直接执行机器码,所以前者慢,后者快,但是因为后者安装应用的时候要编译成机器码,所以其安装速度慢,而且编译生成的机器码要占用额外的内存,所以应用占用的空间就比较大。

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

在传统的GC模式下,当虚拟机触发一次GC,会先暂停所有线程,然后检查所有对象,将符合回收条件的对象进行标记,然后进行回收,最后再恢复线程,这样的话gc速度会快些,但是遇到内存抖动,就会卡顿了。

同时,传统的GC算法导致了【内存碎片化】严重,在一次回收后,很多内存块都会出现不连续的情况,这样会导致寻址变得困难,从而拖慢程序运行速度。而ART虚拟的垃圾回收算法允许GC时对对象的标记和一些对象的清理工作并发进行。同时,ART引入了【移动垃圾回收器】技术,使得碎片化内存能够被对齐,从而能稍微节约一些内存空间。

ART优点:

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

ART缺点

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

Android虚拟机和Java虚拟机之间的区别

JAVA虚拟机与Android虚拟机的区别


JAVA虚拟机、Dalvik虚拟机和ART虚拟机简要对比


这些是你需要知道的Android内存基础.md

解读Android虚拟机工作原理

热修复与插件化基础——Java与Android虚拟机

热修复——深入浅出原理与实现


关于Dalvik、ART、DEX、ODEX、JIT、AOT、OAT

Dalvik,ART与ODEX相爱相生

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注