虚拟机(二)-Dalvik执行java代码流程浅析

本文打算针对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

  1. 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类加载流程
  1. 我们知道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文件。

2)BaseDexClassLoader的findClass加载类过程:

当加载一个类的时候,需要首先获取类加载器,调用类加载器的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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342