Dalvik虚拟机【3】——类加载

概述

什么是类加载

  Dalvik虚拟机从Dex文件提取一个类的数据以及字节码加载到内存中,这个过程就是类加载。
  类加载的输入是Dex文件,输出是内存中的ClassObject对象
  

Dalvik执行应用时流程图

Created with Raphaël 2.1.0 开始 1 启动进程,初始化Dalvik 2 优化与验证Dex文件,生成odex 3 解析加载odex文件至内存 4 加载类生成ClassObject 5 执行字节码 结束

其中第2-4步属于类加载过程

Dex文件优化与验证

  • 过程流程图
    图片标题

  • 文件转换图
    图片标题

      这个过程主要是对Dex文件进行校验和优化,这个过程执行在Dalvik中是一个独立模块,由DexOpt程序执行,在APK安装时或第一次运行时会执行
      由图中可看出,Odex优化主要是在Odex文件中添加依赖库关系寄存器映射关系以及类的索引关系,这些关系的建立会大大提高类加载机制的执行效率,同时,在优化过程中还会根据平台特性对原Dex文件中部分字节码进行替换。
      其中比较关键的是建立类的索引关系,这个过程构造了DexClassLookup结构体,这个对象中记录了类描述符哈希值、类描述符在Dex文件中偏移地址以及类定义区的偏移地址,类加载机制通过这些信息可以非常快速地定位类资源地址并加载类。同时,通过哈希查找的方式极大地提高了类加载过程查找效率

Dex文件解析

Dex文件解析的目的是生成DexFile数据结构与Odex文件关联

DexFile数据结构

DexFile.h

/*
 * Structure representing a DEX file.
 *
 * Code should regard DexFile as opaque, using the API calls provided here
 * to access specific structures.
 */
struct DexFile {
    /* directly-mapped "opt" header */
    const DexOptHeader* pOptHeader;//指向odex文件头

    //这部分是指向Dex文件的
    /* pointers to directly-mapped structs and arrays in base DEX */
    const DexHeader*    pHeader;//Dex文件头
    const DexStringId*  pStringIds;
    const DexTypeId*    pTypeIds;
    const DexFieldId*   pFieldIds;
    const DexMethodId*  pMethodIds;
    const DexProtoId*   pProtoIds;
    const DexClassDef*  pClassDefs;
    const DexLink*      pLinkData;


    /*
     * These are mapped out of the "auxillary" section, and may not be
     * included in the file.
     */
    const DexClassLookup* pClassLookup;//指向odex优化生成的DexClassLookup结构
    const void*         pRegisterMapPool;       // RegisterMapClassPool

    /* points to start of DEX file data */
    const u1*           baseAddr;//Dex文件所在内存的基址

    /* track memory overhead for auxillary structures */
    int                 overhead;

    /* additional app-specific data structures associated with the DEX */
    //void*               auxData;
};

映射关系见下图
图片标题

解析流程

图片标题

执行流程见源代码: dalvik/vm/RawDexFile.cpp:dvmRawDexFileOpen()函数

运行时类加载生成ClassObject

类加载机制的最终目标就是为目标类生成一个ClassObject数据结构的实例对象,并将其存储在运行时环境中随时被执行模块引用执行。

ClassObject数据结构

  这个数据结的代码见文件dalvik/vm/oo/Object.h,其结构图如下
  图片标题

加载流程

  类的加载流程如下图
  图片标题
  流程大致为:虚拟机在获得一个加载类的指令后,其首先确定加载类所属的Dex文件,随后在全局变量中查看虚拟机是否已经完成了对该Dex文件的解析,如果已完成解析,则返回该Dex文件所对应的DexFile数据结构,再根据欲加载类的描述符在DexClassLookup哈希表中查找获取目标类的各个部分数据地址,当得到Dex文件中相关类数据的存储地址后,将通过调用相关的加载函数对指定的各个类信息进行解析并装载,使之以ClassObject类型的数据结构存储于运行时环境之中,并为解释器的执行提供相应类方法的字节码。

代码分析

Created with Raphaël 2.1.0 1 [Jni.cpp] FindClass(JNIEnv* env, const char* name) 2 [Class.cpp] dvmFindClassNoInit(const char* descriptor, Object* loader) 3 [Class.cpp] findClassFromLoaderNoInit(const char* descriptor, Object* loader) 4 [ClassLoader.java] loadClass(String className) 5 [DexPathList] findClass(String name) 6 [dalvik_system_DexFile.cpp] Dalvik_dalvik_system_DexFile_defineClass(const u4* args, JValue* pResult)

  1. static jclass FindClass(JNIEnv* env, const char* name)
    Dalvik创建并初始化完成后,调用这个函数寻找入口Class(这个类加载完成后会调用其Main方法),参数name就是类名,这个函数主要工作是调用dvmGetSystemClassLoader()获取ClassLoader,作为参数调用下面的函数。dvmGetSystemClassLoader通过jni调用Java层的ClassLoader的getSystemClassLoader方法,改方法返回一个PathClassLoader
  2. dvmFindClassNoInit(const char* descriptor, Object* loader)
    这个函数是判断要加载的Class是什么类型,调用不同的函数处理。Class分3种,一种是ArrayClass(*descriptor == ‘[‘),一种是系统Class(loader == NULL),一种是常规Class。这只讨论常规Class
  3. findClassFromLoaderNoInit(const char* descriptor, Object* loader)
    调用dvmLookupClass判断这个类是否已经加载,接着通过jni调用java的ClassLoader的loadClass方法(这个ClassLoader是PathClassLoader)
  4. loadClass(String className)
    ClassLoader.loadClass(String className)方法会又从已经Loaded的Class找一次是否已经加载,没有就接着调用PathClassLoader的findClass方法加载,findClass会调用PathClassLoader的成员pathList的findClass方法。PathClassLoader的pathList是app的安装目录
  5. findClass(String name)

    public Class findClass(String name) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;
    
            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
    
        return null;
    }

    从DexPathList数组每个DexFile查找该Class,找到就退出了。DexFile的loadClassBinaryName会通过jni调用下面的函数

  6. Dalvik_dalvik_system_DexFile_defineClass(const u4* args, JValue* pResult)
    真正执行的类加载的动作,
    1. 调用pDvmDex = dvmGetRawDexFileDex(pDexOrJar->pRawDexFile);得到已经加载在内存中的Dex文件的数据结构DvmDex,若没加载则会加载
    2. 调用dvmDefineClass(pDvmDex, descriptor, loader);实际调用findClassNoInit(descriptor, classLoader, pDvmDex);从DexFile里解析ClassDef,然后通过这个结构从Dex文件里提取生成ClassObject结构,接着把这个类存到一个哈希表里

参考资料

  1. 《Android Dalvik虚拟机结构及机制剖析——第1卷 Dalvik虚拟机结构剖析》
  2. 《Android Dalvik虚拟机结构及机制剖析——第2卷 Dalvik虚拟机各模块机制分析》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值