dalvik的目标平台是Android这样的小RAM,低速率flashmemory,运行标准Linux系统的设备。针对这样的平台特点,要想做到更好,我们须要考虑以下几点:
1、为了降低系统的显存使用,字节码可以多进程共享。但出于安全性考虑,这样的字节码不可以编辑。
2、为了保证响应速率,加载一个新的APP所需时间尽量少。
3、标准Java中把多个类文件分别储存造成了大量的冗余linux运行apk文件,为了节约APP的占用空间,这个问题要解决。
4、加载类的时侯解析类的数组成员会造成额外的消耗,假若改成像C一样直接访问会比较好。
5、字节码verification很有必要,但很慢,我们须要把验证与APP执行分开。
6、字节码optimization(例如指令优化、方法pruning)可以在很大程度上影响执行速率和电瓶消耗。
标准VM都是程序启动时把每单个的类文件解压装入heap,每位进程都有一份copy。这样的做法在显存占用和时间里面都有损失,但便捷了对指令的优化。
如今瞧瞧dalvik是如何做的:
1、多个类被集成进单一的DEX文件。
2、DEX文件在进程间以只读形式共享。
3、byteordering和wordalignment按照localsystem来做调整。
4、字节码verification尽可能提早。
5、需要更改字节码的optimization必须提早进行。
这样做的用处在下边一一介绍。
VMOperation
系统中的应用程序代码以.jar或.apk文件存在。虽然它们都是.zip的文档,只不过多了一些文件头信息。DEX文件也就是解压.apk后的classes.dex文件。classes.dex中的字节码是经过压缩处理的,但是文件脑部不一定是wordaligned,所以不能直接mmap到显存直接执行,而是先解压,之后做一些realignment,optimization,verification操作。下边详尽介绍一下这个过程。
Preparation
做到DEX文件的执行前优化(优化后的DEX称作ODEX,OptimizationDEX),起码有三种形式:
1、VM的JIT技术。优化后的文件置于/data/dalvik-cache目录下。这些方法在模拟器和eng模式下编译的系统中有效,只有这两种情况下操作dalvik-cache目录才不会有权限问题。
2、安装应用程序时,systeminstaller做优化。这须要dalvik-cache目录的写权限。
3、编译系统源码时进行优化。这样优化不会更改jar/apk文件,但会对classes.dex进行优化,优化后的DEX与原文件置于同一个目录下一起写入systemimage。
系统中的/data/dalvik-cache目录属于system/system,权限是0771。储存在这个目录下的ODEX文件被system和应用程序所属的group拥有,权限是0644。DRM-locked的应用程序使用640权限。底线是你可以读你自己的DEX文件和其它的大多数应用程序,但不能创建、修改或删掉它们。
使用JIT和systeminstaller做DEX文件的Preparation要分成三步:
1、由systeminstaller创建dalvik-cache文件夹,这个程序运行在有root权限的installd进程中。
2、classes.dex被解压下来,并在文件背部预留一些空间储存ODEX头信息。
3、为了便捷使用和做一些针对特定系统的微调,把它mmap。例如byte-swapping,structurerealigning等。我们都会做一些像文件偏斜量和数据索引是否越界等方面的基本检测。
编译系统使用一个很复杂的流程来做这种事:启动模拟器,强制对所有相关DEX文件执行JIT优化,最后把优化后的结果从dalvik-cache中提取下来。之所以这样做而不是在PC里面使用一个工具来完成linux虚拟机,在前面解释Optimization时可以看见缘由。
当代码的byte-swapping和align完成时,我们的preparation就完成了。再做完verification和optimization,最后,我们都会把一些相关估算下来的信息添加到ODEX文件的腹部之后开始执行。
dexopt
虽然,假如我们想优化DEX中的类文件的话,最简单最安全的办法就是把所有类加载到VM中之后运行一遍,运行失败的就是没有verification和optimization的。并且,这样会分配一些很难释放的资源。例如,加载本地库时。所以,不能使用运行程序的那种VM来做。
我们的解决方案就是使用dexopt这个程序嵌入式linux驱动程序设计从入门到精通,它会初始化一个VM,加载DEX文件并执行verification和optimization过程。完成后,进程退出,释放所有资源。这个过程中,也可以多个VM使用同一个DEX。filelock会让dexopt只运行一次。
verification
字节码的verification过程涉及到每位DEX文件中的所有类和类中的所有方式中的指令。目标就是检测非法指令序列,这样做完之后,运行的时侯就毋须管了。这个过程中涉及到的许多估算也存在于GC过程中。
出于效率上的考虑,下一节提及的optimization会假定verification早已成功运行通过。默认情况下,dalvik会对所有类进行verification,而只对verification成功的类执行optimization。在进行verification过程中出现失败时,我们不一定会报告(例如在不同的包中调用一个作用范围为包内的类),我们会在执行时抛出一个异常。由于检测每位方式的访问权限很慢。
执行verification成功的类在ODEX文件中有一个flagset,当它们被加载时,就不会再进行verification。linux系统的安全机制会避免这个文件被破坏,但若果你能绕开去,还是能去破坏它的。ODEX文件有一个32-bit的checksum,但只能做一个快速检测。
Optimization
VM类库在第一次运行一段代码时会做一些optimization。例如,把常量池引用替换成指向内部数据结构的表针,一些永远成功的操作或固定的代码被替换成更简单的方式。做这种optimization须要的信息有的只能在运行时得到,有的可以推测下来。
dalvik做的optimization包含下边这种:
1、对于虚方式的调用,把方式索引更改成vtable索引。
2、把field的get/put更改成字节偏斜量。把boolean/byte/char/short等类型的变量合并到一个32-bit的方式,更少的代码可以更有效地借助CPU的I-cache。
3、把一些大量使用的简单方式进行inline,例如String.length()。这样能降低方式调用的开支。
4、删除空方式。
5、加入一些估算好的数据。例如,VM须要一个hashtable来查找类名子,我们就可以在Optimization阶段进行估算,不用放在DEX加载的时侯了。
所有的指令更改都是使用一个Dalvik标准没有定义的指令去替换原有指令。这样,我们就可以让优化和没有优化的指令自由搭配。具体的操作与VM版本有关。
Optimization过程有两个地方须要我们注意:
1、VM假如更新的话,vtable索引和字节偏斜量可能会更新。
2、如果两个DEX互相信赖,而其中一个DEX更新的话,确保优化后的索引和偏斜量有效。
DependenciesandLimitations
优化后的DEX会包含一个它信赖的DEX文件列表,并添加了CRC-32和更改时间。文件列表中包含了dalvik-cache目录下的文件的路径和相应的SHA-1签名。而文件在设备上的timestamp不可信也不能用。另外还有VM版本号。
假如当前DEX所依赖的DEX有更新,我们也须要更新当前DEX。假如我们可以做一个JIT的dexopt调用,更新过程很easy。但若果只能依赖installerdaemon,或则这个DEX被装到ODEX中的话linux运行apk文件,VM只能拒绝它了。
dexopt的输出与平台版本,VM版本有关,想编撰一个运行在PC上,而优化后的输出在其它设备使用的dexopt很难。因而,dexopt是在目标设备上或则目标设备的模拟器上运行。