类加载流程
- 对dex文件进行验证并优化,生成odex文件
- 对odex文件进行解析,解析生成DexFile数据结构,即将文件形式的数据转换成内存中虚拟机可达的数据
- 对指定的类进行加载,操作DexFile提取对应类的字节码,生成ClassObject数据结构
Dexopt
Dalivk中,dex的优化使用的是dexopt,将dex文件优化为Odex文件,最终提交给下一步的加载过程。Odex文件的本质只是在原dex文件的基础上进行优化,并生成.Odex文件进行存储,以提高dalvik虚拟机运行的高效性和安全性。dex的优化过程都是在一个新进程中进行的。
Dexopt主要流程:
- 建立dex的类索引表——使虚拟机快速find dex中某个类的地址
- 寄存器的内存映射——减少odex->DexFile的内存映射操作
- 添加依赖库信息——添加dex需要使用的本地函数库
- dex中字节码的替换——比如类似编译中的内联优化
Dex和Odex文件结构对比图
应用安装到dexopt:
PackageManagerService是用来管理应用安装,卸载,优化等工作的系统服务。和PMS的各种操作最终会通过Java层的Installer—>InstallerConnection—>Socket通信到native的installd.c服务
InstallerConnection.connect():
InstallerConnection.dexopt():
最终会调用到dalvik/dexopt/OptMain.cpp
Dex文件的解析
虚拟机需要访问到可读的Dex数据来进行类加载。因此我们需要将dex文件解析成DexFile的内存中的数据结构。其解析过程实则是将DexFile数据结构中的各个成员变量与Dex文件的各个数据部分相关联。
DexFile的结构体:
需要注意的是这一步是在Dex文件优化之后,所以从这里开始提到的Dex文件都是Odex文件。具体的解析过程不叙述。接下来的过程就是要从DexFile中加载指定的类,并将其装入虚拟机的运行时环境中。
运行时数据装载
到这里,我们需要抽象出另一个数据结构——ClassObject。我们到这里可以梳理一下流程:
Dex的加载一般通过DexClassLoder和PathClassLoder。区别就在于前者可以加载任意路径下的.jar或者.apk,而后者只能加载默认路径下的dex文件,即/data/dalvik-cache。然后两者都继承自BaseDexClassLoader。
后者的构造函数不能设置Odex文件的路径,因此一般用作系统类(其实最终是BootClassLoader加载的)和应用类,前者可以动态的设置Odex路径。
真正的加载函数都是调用自BaseDexClassLoader的父类ClassLoader的loadClass函数:
先调用了findLoadedClass()方法:
先判断虚拟机是否已经加载了这个class,如果加载了就会直接返回。
再看到后面,先调用了parent的loadClass()函数,如果为空才会调用自己的findClass()函数。双亲委派机制(责任链)以及模版方法的设计模式。Android建议我们不要重写loadClass()方法,去重写findClass()方法,就是为了遵循这个机制和生态。因此我们继续跟踪BaseDexClassLoader的findClass()方法:
看到会调用DexPathList的findClass()方法,而这个DexList其实内部维护着一个DexFile的集合。继续跟踪:
遍历DexFile集合,然后去轮询Class。
进入vm\native\dalivk_system_DexFile.cpp中的Dalvik_dalvik_system_DexFile_defineClass
调用dvmGetRawDexFileDex或者dvmGetJarFileDex(如果是jar包)方法去给指向DexFile的指针赋值(其实DexFile是DvmDex的一个成员变量),然后将这个指针传递进dvmDefineClass()函数,而这个函数最终调用了findClassNoInit()函数:
判断是否已经加载,如果没有加载会继续进行加载,通过dexFindClass()方法,返回一个DexClassDef数据结构,这个数据结构是为了方便快速定位类在Dex中的位置,然后最终通过loadClassFromDex()方法给ClassObject指针赋值:
loadClassFromDex()方法流程大致如下:
- 为ClassObject申请内存
- 设置字段信息
- 为超类建立索引
- 加载类接口
- 加载类字段
- 加载类方法
以上为类加载的加载阶段。后面会对ClassObject进行进一步的加工,后面紧接着调用了dvmLinkClass()方法进行Prepare and resolve,主要将符号引用转换成为直接引用,在其中会进一步调用dvmResolveClass()方法,而这个方法实际上是在解析当前被加载类的父类以及接口:
加载完了之后,就会将这个符号引用转换为直接引用,并对GC可见。
/dalvik/vm/oo/Resolve.cpp
对于非基本类型(int、long…),调用dvmFindClassNoInit方法进行加载。如果是基本类型调用dvmFindPrimitiveClass。
/dalvik/vm/oo/Class.cpp
一般加载的非系统类,loader不为NULL,调用findClassFromLoaderNoInit方法进行加载。实际是调用loader的loadClass方法来加载类的。
可以看到dvmResolveClass其实也是加载类,而壳基本上都是hook该函数,大概是因为加载类都会执行这个函数,而且虚拟机解释器,比如const-class指令的实现就会调用dvmResolveClass。
更详细的细节可以参考https://blog.csdn.net/beyond702/article/details/50681453