本文打算针对dalvik执行java代码流程做个简单的梳理。对dalvik有个大框架的认识。
什么是Dalvik虚拟机?
Dalvik是Google公司自己设计用于Android平台的Java虚拟机,它是Android平台的重要组成部分,支持dex格式(Dalvik Executable)的Java应用程序的运行。dex格式是专门为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。Google对其进行了特定的优化,使得Dalvik具有高效、简洁、节省资源的特点。
上一篇介绍了jvm虚拟机之后,dalvik就好上手了。国际惯例,先上图
Dalvik虚拟机执行java文件流程解读:
一、编译器
DVM:sdk dex 不同于 JVM: jdk javac
二、字节码
DVM 处理的字节码较JVM有所不同。
1)JVM编译后生成的是.class文件 最后打成.jar包,而DVM,多了一步.dex文件,最后打包apk。所以JVM执行的是.class文件,而DVM执行的是.dex文件。
JVM .Java----->.class----->.jar
DVM .java----->.class------>.dex-----(加上其它资源文件)---->apk
- JVM字节码由.class文件组成,每个文件一个.class。JVM在运行的时候为每一个类装载字节码。相反的,Dalvik程序包含一个.dex文件,这个文件包含了程序中所有的类。当然,如果做热修复,会有多个.dex文件。
三、可执行文件打包方式和执行效率的区别
1)DVM可执行文件体积更小:dex格式文件就是将多个class文件中公有的部分统一存放,去除冗余信息, 减少了整体文件尺寸。
2)DVM文件处理效率更高:JVM里含有多个.class文件,每个.class文件都包含文件头,这样IO操作效率低。但是DVM中,看不到.class文件了,这是因为dex工具,去掉了app里所有.class文件的冗余信息,再整合到.dex文件中,减少了IO的操作,提高了查找速度。DVM更适合内存和处理器速度有限的系统。
四、虚拟机架构不同
JVM基于栈,DVM基于寄存器。
Java虚拟机基于栈架构:程序在运行时虚拟机需要频繁的从栈上读取或写入数据,这个过程需要更多的指令分派与内存访问次数,会耗费不少CPU时间,对于像手机设备资源有限的设备来说,这是相当大的一笔开销。
Dalvik虚拟机基于寄存器架构:数据的访问通过寄存器间直接传递,这样的访问方式比基于栈方式要快很多。
总结以上的区别:
五、DVM类加载流程
- 我们知道android项目的安装包是.apk文件,在分析类加载流程前,先把apk编译打包流程做个简单地说明:
1)编译 app的源代码,由资源文件生成的R文件(aapt工具),以及有aidl文件生成的java接口文件(aidl工具)。产出为.class文件。
2).class文件和依赖的三方库文件通过dex工具生成Delvik虚拟机可执行的.dex文件,包含了所有的class信息,包括项目自身的class和依赖的class。产出为.dex文件。
3)apkbuilder工具将.dex文件和编译后的资源文件生成未经签名对齐的apk文件。这里编译后的资源文件包括两部分,一是由aapt编译产生的编译后的资源文件,二是依赖的三方库里的资源文件。产出为未经签名的.apk文件。
4)分别由Jarsigner和zipalign对apk文件进行签名和对齐,生成最终的apk文件。
总结为:res + code + aidl --> .class --> .dex-->打包-->签名和对齐 --> apk
2.类加载流程:
Dalvik虚拟机的类加载机制,其输入就是apk中的dex文件,其输出是一个运行时环境中的ClassObject结构体,类加载就是将dex中类的各项资源数据与ClassObject结构体下的各个成员变量以指针的形式进行关联。
先看一下整体流程:
当Android系统启动一个应用的时候,有一步是对dex进行优化,这个过程有一个专门的工具来处理,叫DexOpt。DexOpt的执行过程是在第一次加载dex文件的时候执行的。这个过程会生成一个odex文件,即Optimised Dex。生成可执行文件odex,保存到data/dalvik-cache目录,最后把apk文件中的dex文件删除。执行ODex的效率会比直接执行Dex文件的效率要高很多。
odex的用途是分离程序资源和可执行文件、以及做预编译处理,达到加快软件加载速度和开机速度的目的。另外在5.0以上系统中,相比未做过odex优化,odex转换成oat要花费的的时间更少。
1)优化、验证:(dex - - > odex)
优化的目的是根据特定平台特性,优化dex文件并输出优化后的odex文件,提高程序运行效率。验证的目的是对dex文件中的类数据进行安全性、合法性校验,为虚拟机的安全稳定运行提供保证。
下面看简单看下优化过程:
Android系统通过PackageManagerService来安装APK,在安装的过程,PackageManagerService会通过另外一个类Installer的成员函数dexopt来对APK里面的dex字节码进行优化,Installer通过socket向守护进程installd发送一个dexopt请求,这个请求是由installd里面的函数dexopt来处理的。
android4.4:
frameworks/native/cmds/installd/commands.c
int dexopt(const char *apk_path, uid_t uid, int is_public)
{
…
pid = fork();
if (pid == 0) {
...
if (strncmp(persist_sys_dalvik_vm_lib, "libdvm", 6) == 0) {
run_dexopt(zip_fd, out_fd, apk_path, out_path, dexopt_flags);
} else if (strncmp(persist_sys_dalvik_vm_lib, "libart", 6) == 0) {
run_dex2oat(zip_fd, out_fd, apk_path, out_path, dexopt_flags);
} else {
exit(69); /* Unexpected persist.sys.dalvik.vm.lib value */
}
exit(68); /* only get here on exec failure */
}
...
}
(android后续版本(具体没有调研从哪个版本开始): run_dexopt()方法已经没有了,只支持 run_dex2oat)
那么下面针对Android4.4代码,简单阐述下优化流程,详细细节和源码就不赘述了:
函数dexopt通过fork来创建一个子进程,首先是读取系统属性persist.sys.dalvik.vm.lib的值,值等于libdvm.so,那么该子进程就会调用函数run_dexopt通过执行/system/bin/dexopt来对dex字节码进行优化。实际上也就是由dex文件生成odex文件,最终odex文件被保存在手机的VM缓存目录data/dalvik-cache下(注意!这里所生成的odex文件依旧是以dex为后缀名,格式如:system@priv-app@Settings@Settings.apk@classes.dex)。值等于libart.so,那么该子进程就会调用函数run_dex2oat通过执行/system/bin/dexopt工具来将dex字节码翻译成本地机器码,保存在data/dalvik-cache下,对应一个.oat文件。
那么这里需要注意的是,无论是对dex字节码进行优化,还是将dex字节码翻译成本地机器码,最终得到的结果都是保存在相同名称的一个odex文件里面的,但是前者对应的是一个dey文件(表示这是一个优化过的dex),后者对应的是一个oat文件(实际上是一个自定义的elf文件,里面包含的都是本地机器指令)。通过这种方式,原来任何通过绝对路径引用了该odex文件的代码就都不需要修改了。
最后,还有一个地方需要注意的是,应用程序的安装发生在两个时机,第一个时机是系统启动的时候,第二个时机系统启动完成后用户自行安装的时候。在第一个时机中,系统除了会对/system/app和/data/app目录下的所有APK进行dex字节码到本地机器码的翻译之外,还会对/system/framework目录下的APK或者JAR文件,以及这些APK所引用的外部JAR,进行dex字节码到本地机器码的翻译。这样就可以保证除了应用之外,系统中使用Java来开发的系统服务,也会统一地从dex字节码翻译成本地机器码。
2)解析:( odex - - > dexFile结构体 )
解析的目的是在内存中创建专用的数据结构dexFile来描述odex文件,使虚拟机对odex文件中的各个部分的类数据都是可到达的,为随后的加载某一个类做准备。注意,这个时候只是完成了对odex文件的解析且保存到了内存,但此时odex文件尚未加载到Dalvik虚拟机运行时环境。
3)加载目标类 ( dexFile - - > ClassObject )
类的加载的过程就是从内存中读取DexFile结构,并把对应类加载到Dalvik虚拟机中形成ClassObject的结构对象。ClassObject的成员变量基本上包含了目标类在运行期间所需要用到的全部资源。虚拟机在获取一个类加载指令后,首先确定加载类所属的Dex文件,然后在全局变量中查看虚拟机是否已经完成了对此Dex文件的解析。如果已经完成类的解析,则返回该Dex文件所对应的DexFile数据结构,再根据欲加载类的描述符在DexClassLookup哈希表中查找获取目标类的各个部分数据地址,当得到Dex文件中相关类数据的存储地址后,将通过调用相关的加载函数对指定的各个类信息进行解析并加载,使之以ClassObject类型的数据结构存储于运行时环境中,并为解释器的执行提供相应类方法的字节码。
4)解释器
将提供的类方法的字节码转换为机器码,在android源码中,针对ARM平台,提供了两中解释器:标准的可移植型解释器 和 快速型解释器,实现分别是C和汇编。 系统默认选则快速型解释器。 当在文件系统/data位置增加一个文件local.prop(文件内容为dalvik.vm.execution-mode=int:portable)后,重启系统,解释器就切换为可移植型。
3 . 简单梳理下下Android类加载器执行流程
1)Android 类加载器介绍:
我们在java标准的虚拟机中,如果自定义类加载器,会继承ClassLoader,并重写findClass()方法,在内部调用defineClass()去从一个二进制流中加载Class。那么在Android中,这个defineClass()方法去调用VMClassLoader的defineClass本地静态方法,而这个方法内部除了抛出了异常“UnsupportedOperationException”还有一些属性值,其他什么都没做。那么在Dalvik虚拟中,动态加载类就需要另外由ClassLoader派生出的两个类:DexClassLoader和PathClassLoader,都继承自BaseDexClassLoader。这两个类重载了ClassLoader的findClass()方法,并没有重写loadClass()方法,所以这两个类加载器符合双亲委派模型。
PathClassLoader:是通过构造函数new DexFile(path)来产生DexFile对象的。因此PathClassLoader不能主动从zip包中释放出dex,因此只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在cache中存在缓存的dex文件)。
DexClassLoader:是通过其静态方法loadDex(path, outpath, 0)得到DexFile对象。因此DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。
当加载一个类的时候,需要首先获取类加载器,调用类加载器的loadClass方法,在loadClass方法中先查询当前ClassLoader实例是否加载过此类,有就返回;如果没有,查询parent指定的类加载器是否已经加载过此类,如果已经加载过,就直接返回parent加载的类;如果parent路线上的ClassLoader都没有加载,才调用findClass执行类的加载工作。BaseDexClassLoader的findClass方法调用之前,首先在BaseDexClassLoader的构造方法中生成了DexPathList类的对象pathList,在DexPathList类的构造方法中,利用loadDexFile()方法将dex文件中的类加载到内存中,并生成对应的DexFile对象,进而生成Element对象,用这些Element对象构成Element数组dexElements。在BaseDexClassLoader的findClass方法中,调用了DexPathList类的findClass方法,在DexPathList类的findClass方法里,遍历dexElements元素的DexFile实例,一个个调用loadClassBinaryName()方法,看能不能找到我们想要的类,如果可以,返回这个类。最终返回的是一个ClassObject。
Dalvik的内存分配管理和JVM大体上是一样的,就不赘述了。至于Dalvik线程管理和启动流程,那就参考老罗的文章吧,当然之后也有可能我再来提炼一下。
说明:文章内容参考android4.4版本,本文只是做一个最粗浅的流程总结,并没有针对内容进行具体展开,也没有大段大段地捋代码,这样看着比较累,这就算是一个小笔记吧。
参考文章:
https://blog.csdn.net/Luoshengyang/article/details/18006645
http://www.bubuko.com/infodetail-1113424.html
https://www.cnblogs.com/vendanner/p/4844235.html
https://shuwoom.com/?p=269
https://blog.csdn.net/a594695686/article/details/53378846