Dvm类加载流程

类加载流程

  • 对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文件结构对比图

Dvm类加载流程_第1张图片

应用安装到dexopt:

PackageManagerService是用来管理应用安装,卸载,优化等工作的系统服务。和PMS的各种操作最终会通过Java层的Installer—>InstallerConnection—>Socket通信到native的installd.c服务

InstallerConnection.connect():

Dvm类加载流程_第2张图片

InstallerConnection.dexopt():

Dvm类加载流程_第3张图片

 

最终会调用到dalvik/dexopt/OptMain.cpp

Dvm类加载流程_第4张图片

 

Dex文件的解析

虚拟机需要访问到可读的Dex数据来进行类加载。因此我们需要将dex文件解析成DexFile的内存中的数据结构。其解析过程实则是将DexFile数据结构中的各个成员变量与Dex文件的各个数据部分相关联。

DexFile的结构体:

Dvm类加载流程_第5张图片

需要注意的是这一步是在Dex文件优化之后,所以从这里开始提到的Dex文件都是Odex文件。具体的解析过程不叙述。接下来的过程就是要从DexFile中加载指定的类,并将其装入虚拟机的运行时环境中。

运行时数据装载

​ 到这里,我们需要抽象出另一个数据结构——ClassObject。我们到这里可以梳理一下流程:

Dvm类加载流程_第6张图片

 

Dex的加载一般通过DexClassLoder和PathClassLoder。区别就在于前者可以加载任意路径下的.jar或者.apk,而后者只能加载默认路径下的dex文件,即/data/dalvik-cache。然后两者都继承自BaseDexClassLoader。

Dvm类加载流程_第7张图片

后者的构造函数不能设置Odex文件的路径,因此一般用作系统类(其实最终是BootClassLoader加载的)和应用类,前者可以动态的设置Odex路径。

真正的加载函数都是调用自BaseDexClassLoader的父类ClassLoader的loadClass函数:

Dvm类加载流程_第8张图片

 

先调用了findLoadedClass()方法:

Dvm类加载流程_第9张图片

 

先判断虚拟机是否已经加载了这个class,如果加载了就会直接返回。

再看到后面,先调用了parent的loadClass()函数,如果为空才会调用自己的findClass()函数。双亲委派机制(责任链)以及模版方法的设计模式。Android建议我们不要重写loadClass()方法,去重写findClass()方法,就是为了遵循这个机制和生态。因此我们继续跟踪BaseDexClassLoader的findClass()方法:

Dvm类加载流程_第10张图片

看到会调用DexPathList的findClass()方法,而这个DexList其实内部维护着一个DexFile的集合。继续跟踪:

Dvm类加载流程_第11张图片

遍历DexFile集合,然后去轮询Class。

Dvm类加载流程_第12张图片

进入vm\native\dalivk_system_DexFile.cpp中的Dalvik_dalvik_system_DexFile_defineClass

Dvm类加载流程_第13张图片

调用dvmGetRawDexFileDex或者dvmGetJarFileDex(如果是jar包)方法去给指向DexFile的指针赋值(其实DexFile是DvmDex的一个成员变量),然后将这个指针传递进dvmDefineClass()函数,而这个函数最终调用了findClassNoInit()函数:

Dvm类加载流程_第14张图片

判断是否已经加载,如果没有加载会继续进行加载,通过dexFindClass()方法,返回一个DexClassDef数据结构,这个数据结构是为了方便快速定位类在Dex中的位置,然后最终通过loadClassFromDex()方法给ClassObject指针赋值:

Dvm类加载流程_第15张图片

loadClassFromDex()方法流程大致如下:

  • 为ClassObject申请内存
  • 设置字段信息
  • 为超类建立索引
  • 加载类接口
  • 加载类字段
  • 加载类方法

以上为类加载的加载阶段。后面会对ClassObject进行进一步的加工,后面紧接着调用了dvmLinkClass()方法进行Prepare and resolve,主要将符号引用转换成为直接引用,在其中会进一步调用dvmResolveClass()方法,而这个方法实际上是在解析当前被加载类的父类以及接口:

Dvm类加载流程_第16张图片

加载完了之后,就会将这个符号引用转换为直接引用,并对GC可见。

/dalvik/vm/oo/Resolve.cpp

Dvm类加载流程_第17张图片

对于非基本类型(int、long…),调用dvmFindClassNoInit方法进行加载。如果是基本类型调用dvmFindPrimitiveClass。

/dalvik/vm/oo/Class.cpp

Dvm类加载流程_第18张图片

一般加载的非系统类,loader不为NULL,调用findClassFromLoaderNoInit方法进行加载。实际是调用loader的loadClass方法来加载类的。

Dvm类加载流程_第19张图片

 

可以看到dvmResolveClass其实也是加载类,而壳基本上都是hook该函数,大概是因为加载类都会执行这个函数,而且虚拟机解释器,比如const-class指令的实现就会调用dvmResolveClass。

更详细的细节可以参考https://blog.csdn.net/beyond702/article/details/50681453

 

 

 

 

你可能感兴趣的:(android)