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

来源:互联网 发布:java native关键字 编辑:程序博客网 时间:2024/06/07 01:23

  • 概述
    • 什么是类加载
    • Dalvik执行应用时流程图
  • Dex文件优化与验证
  • Dex文件解析
    • DexFile数据结构
    • 解析流程
  • 运行时类加载生成ClassObject
    • ClassObject数据结构
    • 加载流程
    • 代码分析
  • 参考资料

概述

什么是类加载

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

Dalvik执行应用时流程图

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

其中第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.01 [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虚拟机各模块机制分析》
0 0
原创粉丝点击