http://blog.csdn.net/roland_sun/article/details/46877563 原文如下:

Android系统中,所有的类定义以及具体的代码都是包含在DEX文件中的。但是,一个功能丰富的程序往往都比较复杂,由很多类组成。

而每一个类,都由一个所谓类描述符(Class Descriptor)的字符串来唯一标识,两个类不可能有同一个类描述符。类描述符不仅包含类名,还包含了类所在的包名。例如,如果你的类所在包名是“com.trendmicro.mars”,且类名是“Test”的话,那么这个类的类描述符就是“Lcom/trendmicro/mars/Test;”。

但是,如果要从一个DEX文件内的众多类中找出那个你想使用的类,仅仅通过逐一比较DEX文件中所有类的类描述符字符串的话,速度往往会比较慢,用户体验会比较差。

Dalvik虚拟机为了解决这个问题,在加载和验证一个DEX文件的时候,会附带生成一个所谓的DexClassLookup结构体,来加快类的查找速度。

  1. struct DexClassLookup {
  2. int     size;
  3. int     numEntries;
  4. struct {
  5. u4      classDescriptorHash;
  6. int     classDescriptorOffset;
  7. int     classDefOffset;
  8. } table[1];
  9. };

结构体最开始是一个int型的size,表示了这个DexClassLookup结构体到底要占用多少字节的空间。这个大小也包含了size变量本身的4字节。

接下来的int型numEntries,表示DexClassLookup到底包含了多少个条目。

最后定义了一个内部结构体,存放具体的数据。不过table[1]并不是表示DexClassLookup中只包含一项这个结构体数据,这里只表示下面的是一个数组,具体有多少项,是由前面的numEntries来指定的。

下面,我们来看看,到底这个结构体是如何生成的(代码位于\dalvik\libdex\DexFile.cpp内):

  1. DexClassLookup* dexCreateClassLookup(DexFile* pDexFile)
  2. {
  3. DexClassLookup* pLookup;
  4. int allocSize;
  5. int i, numEntries;
  6. int numProbes, totalProbes, maxProbes;
  7. numProbes = totalProbes = maxProbes = 0;
  8. assert(pDexFile != NULL);
  9. numEntries = dexRoundUpPower2(pDexFile->pHeader->classDefsSize * 2);
  10. allocSize = offsetof(DexClassLookup, table)
  11. + numEntries * sizeof(pLookup->table[0]);
  12. pLookup = (DexClassLookup*) calloc(1, allocSize);
  13. if (pLookup == NULL)
  14. return NULL;
  15. pLookup->size = allocSize;
  16. pLookup->numEntries = numEntries;
  17. for (i = 0; i < (int)pDexFile->pHeader->classDefsSize; i++) {
  18. const DexClassDef* pClassDef;
  19. const char* pString;
  20. pClassDef = dexGetClassDef(pDexFile, i);
  21. pString = dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
  22. classLookupAdd(pDexFile, pLookup,
  23. (u1*)pString - pDexFile->baseAddr,
  24. (u1*)pClassDef - pDexFile->baseAddr, &numProbes);
  25. if (numProbes > maxProbes)
  26. maxProbes = numProbes;
  27. totalProbes += numProbes;
  28. }
  29. ...
  30. return pLookup;
  31. }

代码首先确定到底要存放多少条数据。注意,并不是有多少个类就生成多少个条目的。可以看到,具体生成的条目数是类的个数乘以2,然后再算下一个2的幂次方。比如,如果我有5个类的话,那么首先乘以2,得到10,下一个2的幂次方数字是16,,就会生成16个条目。为什么要这么做?我觉得是为了尽量减少Hash碰撞的情况发生。

知道了要创建多少条目的数据后,就可以知道到底要开辟多大的空间来存放这个结构体数据(按照现在的定义,分配空间的计算公式是8+numEntries*12),并且在内存中为这个结构体分配一段连续的空间。接着,对DexClassLookup结构体的前两个变量size和numEntries赋值。

最后,就是要来填写具体的数据了。程序中会遍历DEX文件中包含的每一个类,逐一获得它们的DexClassDef结构和类描述符,且传递给classLookupAdd函数,让它来填写对应该类的快速查找数据(代码位于\dalvik\libdex\DexFile.cpp内):

  1. static void classLookupAdd(DexFile* pDexFile, DexClassLookup* pLookup,
  2. int stringOff, int classDefOff, int* pNumProbes)
  3. {
  4. const char* classDescriptor =
  5. (const char*) (pDexFile->baseAddr + stringOff);
  6. const DexClassDef* pClassDef =
  7. (const DexClassDef*) (pDexFile->baseAddr + classDefOff);
  8. u4 hash = classDescriptorHash(classDescriptor);
  9. int mask = pLookup->numEntries-1;
  10. int idx = hash & mask;
  11. int probes = 0;
  12. while (pLookup->table[idx].classDescriptorOffset != 0) {
  13. idx = (idx + 1) & mask;
  14. probes++;
  15. }
  16. pLookup->table[idx].classDescriptorHash = hash;
  17. pLookup->table[idx].classDescriptorOffset = stringOff;
  18. pLookup->table[idx].classDefOffset = classDefOff;
  19. *pNumProbes = probes;
  20. }

函数首先调用classDescriptorHash,计算出类描述符对应的一个Hash值,这是一个数字。

然后,代码会根据条目数的多少,计算出一个mask,并且和前面计算的Hash值与以下,算出该条数据在数组中存放位置的下标。前面说过了,数据的条目数一定是2的幂次方。比如,如果是8的话,下标值就取Hash值得后三位,16的话就取Hash值得后四位。这也就解释了,为什么快速查找数据的条目数必须是2的幂次方了。

接下来,看看数组中这个下标对应的条目是不是已经存放了别的类的信息。这种情况,就是碰撞,两个不同的类被映射到了同一个数字上。一旦出现了碰撞的情况话,程序接着用了一种非常简单的处理方法,直接将下标加1,和mask再与一下,得到接着要尝试存放的那个位置,再重头判断一下,直到找到一个没有被用过的位置。但是,这样处理,有可能会占了别的类应该存放的位置,使得性能下降。所以,前面的代码在计算条目数的时候,人为的乘以2,降低了碰撞的概率。不过这样处理的话,存储空间会比较浪费。最后,找到了一个空的位置后,会将对应类的具体数据,包括前面算的类描述符Hash值、类描述符字符串和该类的DexClassDef相对于DEX文件头的偏移量等信息,存放在该位置上。

好了,看完了如何生成DexClassLookup结构体数据,我们再来看看Dalvik虚拟机是如何利用它来加快类的查找速度的(代码位于\dalvik\libdex\DexFile.cpp内):

  1. const DexClassDef* dexFindClass(const DexFile* pDexFile,
  2. const char* descriptor)
  3. {
  4. const DexClassLookup* pLookup = pDexFile->pClassLookup;
  5. u4 hash;
  6. int idx, mask;
  7. hash = classDescriptorHash(descriptor);
  8. mask = pLookup->numEntries - 1;
  9. idx = hash & mask;
  10. while (true) {
  11. int offset;
  12. offset = pLookup->table[idx].classDescriptorOffset;
  13. if (offset == 0)
  14. return NULL;
  15. if (pLookup->table[idx].classDescriptorHash == hash) {
  16. const char* str;
  17. str = (const char*) (pDexFile->baseAddr + offset);
  18. if (strcmp(str, descriptor) == 0) {
  19. return (const DexClassDef*)
  20. (pDexFile->baseAddr + pLookup->table[idx].classDefOffset);
  21. }
  22. }
  23. idx = (idx + 1) & mask;
  24. }
  25. }

查找的代码就非常简单了,还是先对要查找类的类描述符,用同样的算法计算一下Hash值,根据条目的数目,取Hash值相应的低几位。以这个值为下标,尝试读取数组中对应位置的数据。如果没有碰撞情况发生的话,一次就能找到你想找的类。如果有碰撞情况的话,还是试着循环查找下一个位置的信息。所以,可以看出来,查找的时候,是将字符串的逐个字符比较转变成了一个四字节数字的比较,速度大大加快了。

对每一个DEX文件来说,其实只需要在最开始计算一次就可以了,没必要每次加载的时候都计算一遍。大家知道,一个DEX文件在第一次被加载的时候,Dalvik虚拟机会对其进行验证和优化,从而以后再次加载这个DEX文件的时候,可以直接读取优化过得ODEX文件,加快加载速度。而在ODEX文件中,其实就包含了对应于这个DEX文件的DexClassLookup结构体数据,直接mmap到内存就好了,不需要再算了。

这里再引申讨论一下,为什么DEX文件中不直接包含对应的DexClassLookup结构体数据呢,就像ELF文件一样?理论上其实是可以的,因为这些都是静态数据,不会在运行的时候改变。我想唯一的解释估计是android不想把快速查找的功能和DEX绑死,而是由Dalvik虚拟机自己实现。这样,不同版本的虚拟机完全可以使用不同的快速查找算法。

Dalvik虚拟机中DexClassLookup结构解析的更多相关文章

  1. java对象在内存中的结构(HotSpot虚拟机)

    一.对象的内存布局 HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header).实例数据(Instance Data)和对齐填充(Padding). 从上面的这张图里面可以 ...

  2. 深入理解JAVA虚拟机原理之Dalvik虚拟机(三)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 本文是Android虚拟机系列文章的第三篇,专门介绍Andorid系统上曾经使用 ...

  3. Dalvik虚拟机JNI方法的注册过程分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8923483 在前面一文中,我们分析了Dalvi ...

  4. Dalvik虚拟机的启动过程分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8885792 在Android系统中,应用程序进 ...

  5. 【转】Dalvik虚拟机的启动过程分析

    在Android系统中,应用程序进程都是由Zygote进程孵化出来的,而Zygote进程是由Init进程启动的.Zygote进程在启动时会创建一个Dalvik虚拟机实例,每当它孵化一个新的应用程序进程 ...

  6. Dalvik虚拟机垃圾收集(GC)过程分析

    前面我们分析了Dalvik虚拟机堆的创建过程,以及Java对象在堆上的分配过程. 这些知识都是理解Dalvik虚拟机垃圾收集过程的基础.垃圾收集是一个复杂的过程,它要将那些不再被引用的对象进行回收.一 ...

  7. Dalvik虚拟机简要介绍和学习计划

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8852432 我们知道,Android应用程序是 ...

  8. Android虚拟机器学习总结Dalvik虚拟机创建进程和线程分析

    Dalvik调用一个成员函数时,虚拟机,假设发现,该成员函数是一个JNI办法,然后,它会直接跳转到其地址来运行.也就是说.JNI方法是直接在本地操作系统上运行的.而不是由Dalvik虚拟机解释器运行. ...

  9. java对象在内存中的结构

    在HotspotJVM中,32位机器下,Integer对象的大小是int的几倍? 我们都知道在java语言规范已经规定了int的大小是4个字节,那么Integer对象的大小是多少呢?要知道一个对象的大 ...

随机推荐

  1. Monkey用真机做测试的步骤

    1 必备条件 1) 手机需要先获取root权限: 2) 手机和电脑相连(电脑可以访问手机里面的文件) 2  操作步骤 1) 使用adb devices 命令查看电脑手机是否相连: 下图表示手机已连上电 ...

  2. Python列表操作大全(非常全)

    Python列表操作大全(非常全!!!) 对于python列表的理解可以和C语言里面的数组进行比较性的记忆与对照,它们比较相似,对于python里面列表的定义可以直接用方括号里加所包含对象的方法,并且 ...

  3. TW实习日记:第26天

    这周组长休年假去了,并且之前主要负责的项目也已经上线了,可以说没那么忙了,手头就一个协助别的组做的移动端项目.可是这个项目特别坑,由于网端是9年前的项目,导致后台的接口有非常多的问题,并且入参多得令人 ...

  4. (一)Spring Boot修改内置Tomcat端口号--解决tomcat端口被占用的问题

    Spring Boot 内置Tomcat默认端口号为8080,在开发多个应用调试时很不方便,本文介绍了修改 Spring Boot内置Tomcat端口号的方法. 一.EmbeddedServletCo ...

  5. 小程序开发中,纯css实现内容收起折叠功能

    不多说,直接上代码: wxml页面: <!--收起折叠 begin--> <view style='width:100%;background:#fff;border-top:1px ...

  6. Paper Reading - Learning like a Child: Fast Novel Visual Concept Learning from Sentence Descriptions of Images ( ICCV 2015 )

    Link of the Paper: https://arxiv.org/pdf/1504.06692.pdf Innovations: The authors propose the Novel V ...

  7. 前端整合MathjaxJS的配置笔记

    这篇文章是我给Pinghsu主题添加数学公式功能的一个小教程,包含我大量的官方文档阅读后的实践,跟着这篇配置教程走,你可以做到给任何一个需要数学公式的站点添加支持. 教程如标题所述是针对 Mathja ...

  8. Microservices with Spring Boot

    找到一套比较不错的Spring Boot/Cloud入门教程,推荐一下. https://dzone.com/users/1041459/ranga_pec.html

  9. location 匹配规则 (NGINX)

    转:https://moonbingbing.gitbooks.io/openresty-best-practices/ngx/nginx_local_pcre.html location 匹配规则 ...

  10. “Hello world!”团队—团队选题展示(视频展示说明)

    本次博客的主要内容基本分为以下两方面: 一.视频截图展示 二.视频简要说明 博客内容展示: 视频截图1: 简要说明:这是组长在视频前期简要介绍我们这款游戏项目的内容.从可行性和需求市场方面进行了简要阐 ...