在Windows程序执行时,CPU将程序的指令和数据加载到内存执行。相似的,Android程序执行时,要将程序的指令和数据加载到虚拟机的运行时环境。
Android的apk程序本质是一个压缩包,里面包含了classes.dex可执行文件,AndroidManifest.xml配置文件和资源文件等,将任意hello.apk修改为hello.zip解压就能看到。这和在Windows下将.doc的word文档修改为.zip解压类似。
Dalvik虚拟机的类加载机制,其输入就是apk中的dex文件,其输出是一个运行时环境中的ClassObject结构体,类加载就是将dex中类的各项资源数据与ClassObject结构体下的各个成员变量以指针的形式进行关联。
下面看一下类加载机制的工作流程。
- dex文件校验及优化
验证的目的是对dex文件中的类数据进行安全性、合法性校验,为虚拟机的安全稳定运行提供保证。优化的目的是根据特定平台特性,优化dex文件并输出优化后的odex文件,提高程序运行效率。
- 对优化后的odex文件进行解析
其目的是在内存中创建专用的数据结构来描述odex文件,使虚拟机对odex文件中的各个部分的类数据都是可到达的,为随后的加载某一个类做准备。注意,这个时候只是完成了对odex文件的解析且保存到了内存,但此时odex文件尚未加载到Dalvik虚拟机运行时环境。
- 加载指定类
根据Dalvik虚拟机执行需要,从已被解析的odex文件中提取二进制Dalvik字节码,并将其封装进运行时数据结构,以供解释器执行。该运行时数据结构就是一个ClassObject结构体对象,也称类对象。
值得注意的是,dex的校验和优化在Android Dalvik虚拟机中被设计为独立的功能模块,Android系统新建一个虚拟机专门用于dex文件校验和优化,并在功能结束时释放虚拟机资源。这种设计很符合UNIX中只做一件事情,把它做好的设计哲学。
从odex的文件结构可以看出,它比dex文件多出3部分,其中最重要的工作体现在依赖库信息和类索引信息两部分上,通过这两部分附加数据,Dalvik虚拟机可以更快速的找到dex中依赖库和类的信息,提高程序执行效率。
在后面的表述中,我们把优化过的odex文件亦称为dex文件。dex文件在完成校验和优化后,下面的步骤就是进行解析。通过dex文件解析,在内存中生成DexFile结构体的一个实例,其结构体成员与dex文件格式中的数据元是一一对应的关系,即dex文件在内存中的表现。
类的加载的过程就是从内存中读取DexFile结构,并把对应类加载到Dalvik虚拟机中形成ClassObject的结构对象。ClassObject的成员变量基本上包含了目标类在运行期间所需要用到的全部资源。虚拟机在获取一个类加载指令后,首先确定加载类所属的Dex文件,然后在全局变量中查看虚拟机是否已经完成了对此Dex文件的解析。如果已经完成类的解析,则返回该Dex文件所对应的DexFile数据结构,再根据欲加载类的描述符在DexClassLookup哈希表中查找获取目标类的各个部分数据地址,(上图的第3部分类索引信息画的有些问题,正确的是每个类都有这样一个结构。)当得到Dex文件中相关类数据的存储地址后,将通过调用相关的加载函数对指定的各个类信息进行解析并加载,使之以ClassObject类型的数据结构存储于运行时环境中,并为解释器的执行提供相应类方法的字节码。
当虚拟机完成类的加载后,解释器会对加载的代码进行检查,验证是否符合虚拟机的执行规范,最后调用dvmInterpret函数进行解释器初始化并开始执行字节码。dvmInterpret函数有一段关键代码。
/* 初始化解释器工作环境 */
self->interpSave.method = method;
self->interpSave.curFrame = (u4*) self->interpSave.curFrame;
self->interpSave.pc = method.insns;
这个执行进程self首先取得了将要执行方法的method指针,随后在第3行代码中将method->insns可执行代码区的首地址赋值给了执行进程的pc指针的self->interpSave.pc,随后解释器的pc指针将会逐一读入指令并执行相关字节码。