Android 2.3中的LinearAlloc

2020-12-03 14:42:29 浏览数 (2)

原因

2.3版本Apk安装时 , 会进行Dexopt , 如果单个Dex中的class过大/method过多 , 就会导致LinearAlloc为Class/Method的内存分配不足 , 从而让Dexopt进程挂掉.

而如果存在Multidex的话 , Multidex会为多个Dex执行多次Dexopt操作 , 所以 , 如果也存在的话 , 也会导致LinearAlloc超限.

同时 , 在运行时加载Class文件时 , 也会使用LinearAlloc为Interface、Method分配内存 , 如果超出5M限制 , 就会报LinearAlloc exceeded capacity异常 , 会导致DVM虚拟机异常.

在加载类时 , 会使用LinearAlloc为Class的以下属性分配内存空间 :

  • Interfaces : 大小 : count * (sizeof(ClassObject*))
  • InstantceFields : 大小 : count * sizeof(InstField)
  • Direct Methods : 大小 : count * sizeof(Method)
  • Virtual Methods : 大小 : count * sizeof(Method)
  • vtable : 大小 : sizeof(Method*) *maxCount
  • iftable : 大小 : sizeof(InterfaceEntry) * ifCount
代码语言:javascript复制
01-24 11:14:54.884: I/dalvikvm(12382): DexOpt: not resolving ambiguous class 'Lorg/apache/commons/codec/DecoderException;'
01-24 11:14:55.935: E/dalvikvm(12382): LinearAlloc exceeded capacity (5242880), last=80
01-24 11:14:55.935: E/dalvikvm(12382): VM aborting
01-24 11:14:56.265: I/DEBUG(1257): debuggerd: 2013-01-24 11:14:56
01-24 11:14:56.265: I/DEBUG(1257): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
01-24 11:14:56.265: I/DEBUG(1257): Build fingerprint: 'htc_asia_wwe/htc_ace/ace:2.3.5/GRJ90/228204.4:user/release-keys'
...
01-24 11:14:56.306: W/installd(1263): DexInv: --- END '/data/app/com.realcloud.loochadroid.campuscloud-1.apk' --- status=0x000b, process failed
01-24 11:14:56.306: E/installd(1263): dexopt failed on '/data/dalvik-cache/data@app@com.realcloud.loochadroid.campuscloud-1.apk@classes.dex' res = 11
01-24 11:14:56.306: W/PackageManager(1379): Package couldn't be installed in /data/app/com.realcloud.loochadroid.campuscloud-1.apk
01-24 11:14:56.466: D/dalvikvm(1379): GC_EXPLICIT freed 2597K, 40% free 11337K/18759K, external 1573K/2080K, paused 146ms

解决方案

  1. 在打MultiDex的时候 , 添加dx的参数--ser-max-idx-number=48000 , 让每个Dex的最大方法数最大为48000 , 避免出现LinearAlloc
代码语言:javascript复制
dexOptions {
            javaMaxHeapSize "2g"
            additionalParameters  = '--multi-dex'
            // 设置Dex的最大方法数
            additionalParameters  = '--set-max-idx-number=62000'
        }
  1. 配置Proguard , 优化代码 , 减少方法和类数量.
  2. Dex过多会导致2.x的版本 , 可能会出现ANR的问题 , 可以通过多进程Dexopt来处理该问题.

流程

  1. 以2.3版本为例LinearAlloc最大内存为 : 5M

image.png

在调用dvmLinearAllocCreate函数中 , 会通过ashmem_create_region创建一片5M大小的内存空间

image.png

  1. class.cc中 , 会调用dvmClassStartup函数创建LinearAllocHdr对象 , 并且赋值给gDvm.pBootLoaderAlloc用于后续的dex加载

image.png

  1. LinearAlloc.cc中调用dvmLinearAlloc为classLoader分配大小

image.png

  1. 在ClassLoader将Odex文件加载入内存后, 校验Odex的CRC32、签名、Magic等 , 并且构建DexFile结构用于标识Method、Class等方法入口.

当使用到Class时 , 将会调用findClass来加载类文件 , 同时使用LinearAlloc为interface、Method分配内存.

ClassLoder defineClass借图

  1. findClassNoInit函数比较简单 , 主要有以下步骤 :
  • 从HashTable中查找descrioptor对应的class类
  • 找到Class对应的DexFile对象
  • 从DexFile中加载Class对象
  • 将Class对象添加到HashTable中
  • 开始Resolve Class
代码语言:javascript复制
static ClassObject* findClassNoInit(const char* descriptor, Object* loader,
    DvmDex* pDvmDex)
{
     ... 
     // 先从HashTable中查找类
    clazz = dvmLookupClass(descriptor, loader, true);
    if (clazz == NULL) {
        ......
        // 如果没找到对应Dex , 则从BootPath中查找
        if (pDvmDex == NULL) {
            assert(loader == NULL);     /* shouldn't be here otherwise */
            pDvmDex = searchBootPathForClass(descriptor, &pClassDef);
        } else {
           // 从DexFile中找到Class
           pClassDef = dexFindClass(pDvmDex->pDexFile, descriptor);
        }

        ......

       // 如果找到了Class , 则从Dex文件中加载该类
       clazz = loadClassFromDex(pDvmDex, pClassDef, loader);
         ...
        //  将Class对象添加到HashTable中
       if (!dvmAddClassToHash(clazz)) {
            // 如果添加失败 , 则开始查找class对象
            clazz = dvmLookupClass(descriptor, loader, true);
            assert(clazz != NULL);
            goto got_class;
        }
         // 开始Resolve Class , 也就是Link Class
       if (!dvmLinkClass(clazz)) {
            ......
        }    
    ...
    return clazz;
}
  1. dexDefineClass
代码语言:javascript复制
const DexClassDef* dexFindClass(const DexFile* pDexFile,
    const char* descriptor)
{
    const DexClassLookup* pLookup = pDexFile->pClassLookup;
    u4 hash;
    int idx, mask;
    hash = classDescriptorHash(descriptor);
    mask = pLookup->numEntries - 1;
    idx = hash & mask;
    while (true) {
        int offset;
        // 遍历DexFile table的ClassDescriotor
        offset = pLookup->table[idx].classDescriptorOffset;
       if (offset == 0)
            return NULL;
        // 如果找到了descriptor对应的hash值
        if (pLookup->table[idx].classDescriptorHash == hash) {
            const char* str;
            // 根据基址找到字符串
            str = (const char*) (pDexFile->baseAddr   offset);
            if (strcmp(str, descriptor) == 0) {  
               // 返回DexClasDef对象 , 对应Class的信息
               return (const DexClassDef*)
                    (pDexFile->baseAddr   pLookup->table[idx].classDefOffset);
            }
        }
        idx = (idx   1) & mask;
    }
}
  1. loadClassFromDex中 , 会调用loadClassFromDex0加载类 , 会通过LinearAlloc分配
  • Interfaces
  • InstantceFields
  • Direct Methods
  • Virtual Methods
代码语言:javascript复制
static ClassObject* loadClassFromDex0(DvmDex* pDvmDex,
    const DexClassDef* pClassDef, const DexClassDataHeader* pHeader,
    const u1* pEncodedData, Object* classLoader)
{
      // 返回的Class对象
     ClassObject* newClass = NULL;
     ... 
     // 初始化Class对象
     if (classLoader == NULL &&
        strcmp(descriptor, "Ljava/lang/Class;") == 0) {
        assert(gDvm.classJavaLangClass != NULL);
        newClass = gDvm.classJavaLangClass;
    } else {
        size_t size = classObjectSize(pHeader->staticFieldsSize);
        newClass = (ClassObject*) dvmMalloc(size, ALLOC_NON_MOVING);
    }
     // 填充super
     newClass->super = (ClassObject*) pClassDef->superclassIdx;
    ...
    const DexTypeList* pInterfacesList;
    // 得到Interface的列表
   pInterfacesList = dexGetInterfacesList(pDexFile, pClassDef);
    if (pInterfacesList != NULL) {
        newClass->interfaceCount = pInterfacesList->size;
        // 使用LinearAlloc分配interface列表
        newClass->interfaces = (ClassObject**) dvmLinearAlloc(classLoader,
                newClass->interfaceCount * sizeof(ClassObject*));

        for (i = 0; i < newClass->interfaceCount; i  ) {
            const DexTypeItem* pType = dexGetTypeItem(pInterfacesList, i);
            newClass->interfaces[i] = (ClassObject*)(u4) pType->typeIdx;
        }
        // 通过mprotect设置ReadOnly
        dvmLinearReadOnly(classLoader, newClass->interfaces);
    }  
    // 加载static属性列表
   if (pHeader->staticFieldsSize != 0) {
        /* static fields stay on system heap; field data isn't "write once" */
        int count = (int) pHeader->staticFieldsSize;
        u4 lastIndex = 0;
        DexField field;

        newClass->sfieldCount = count;
        for (i = 0; i < count; i  ) {
            dexReadClassDataField(&pEncodedData, &field, &lastIndex);
            loadSFieldFromDex(newClass, &field, &newClass->sfields[i]);
        }
    }
   // 初始化实例属性
   if (pHeader->instanceFieldsSize != 0) {
        int count = (int) pHeader->instanceFieldsSize;
        u4 lastIndex = 0;
        DexField field;

        newClass->ifieldCount = count;
        // 使用linearAlloc分配ifields
        newClass->ifields = (InstField*) dvmLinearAlloc(classLoader,
                count * sizeof(InstField));
        for (i = 0; i < count; i  ) {
            dexReadClassDataField(&pEncodedData, &field, &lastIndex);
            loadIFieldFromDex(newClass, &field, &newClass->ifields[i]);
        }
        dvmLinearReadOnly(classLoader, newClass->ifields);
    }
   // 初始化directMethod
   if (pHeader->directMethodsSize != 0) {
        int count = (int) pHeader->directMethodsSize;
        u4 lastIndex = 0;
        DexMethod method;
        newClass->directMethodCount = count;
        // 通过LinearAlloc分配directMethods内存
        newClass->directMethods = (Method*) dvmLinearAlloc(classLoader,
                count * sizeof(Method));
         ...
        dvmLinearReadOnly(classLoader, newClass->directMethods);
    }
    // 初始化虚函数
   if (pHeader->virtualMethodsSize != 0) {
        int count = (int) pHeader->virtualMethodsSize;
        u4 lastIndex = 0;
        DexMethod method;

        newClass->virtualMethodCount = count;
        // 通过LinearAlloc分配virtualMethod内存空间
        newClass->virtualMethods = (Method*) dvmLinearAlloc(classLoader,
                count * sizeof(Method));
        ...
        dvmLinearReadOnly(classLoader, newClass->virtualMethods);
    }  
    newClass->sourceFile = dexGetSourceFile(pDexFile, pClassDef);
    return newClass;
}

8.最后通过dvmLinkClass开始链接Class

代码语言:javascript复制
bool dvmLinkClass(ClassObject* clazz)
{
    ......
    if (clazz->status == CLASS_IDX) {
         
        superclassIdx = (u4) clazz->super;
        clazz->super= NULL;
        // 修改状态为CLASS_LOADED
        clazz->status = CLASS_LOADED;

        if (superclassIdx != kDexNoIndex) {
          // 查找已经解析好的父类Class对象
           ClassObject* super = dvmResolveClass(clazz, superclassIdx, false);
           ... // 错误校验
            // 设置父类Class对象地址
            dvmSetFieldObject((Object *)clazz,
                              OFFSETOF_MEMBER(ClassObject, super),
                              (Object *)super);
        }
        // 如果类的interface大于0
       if (clazz->interfaceCount > 0) {
            // 解析interface
            dvmLinearReadWrite(clazz->classLoader, clazz->interfaces);
            for (i = 0; i < clazz->interfaceCount; i  ) {
                assert(interfaceIdxArray[i] != kDexNoIndex);
                clazz->interfaces[i] =
                    dvmResolveClass(clazz, interfaceIdxArray[i], false);
                ......
            }
            dvmLinearReadOnly(clazz->classLoader, clazz->interfaces);
        }
    }
    ... 
    if (strcmp(clazz->descriptor, "Ljava/lang/Object;") == 0) {
        ......
    } else {
        if (dvmIsFinalClass(clazz->super)) {
             // 校验父类是否为final类
            goto bail;
        } else if (dvmIsInterfaceClass(clazz->super)) {
            // 校验父类为interface
            goto bail;
        } else if (!dvmCheckClassAccess(clazz, clazz->super)) {
            // 校验父类是否允许访问
            goto bail;
        }
        ...
    }
     // 开始创建vtable
    if (dvmIsInterfaceClass(clazz)) {
         // 如果该类是interface的话 , 则不需要创建vtable
        int count = clazz->virtualMethodCount;
        if (count != (u2) count) {
            ALOGE("Too many methods (%d) in interface '%s'", count,
                 clazz->descriptor);
            goto bail;
        }
        dvmLinearReadWrite(clazz->classLoader, clazz->virtualMethods);
        for (i = 0; i < count; i  )
            clazz->virtualMethods[i].methodIndex = (u2) i;
        dvmLinearReadOnly(clazz->classLoader, clazz->virtualMethods);
    } else {
        // 开始创建vtable
        if (!createVtable(clazz)) {
            ALOGW("failed creating vtable");
            goto bail;
        }
    }
     //  创建interface table
   if (!createIftable(clazz))
        goto bail;
    ...
bail:
    if (!okay) {
        clazz->status = CLASS_ERROR;
        if (!dvmCheckException(dvmThreadSelf())) {
            dvmThrowVirtualMachineError(NULL);
        }
    }
    if (interfaceIdxArray != NULL) {
        free(interfaceIdxArray);
    }
    return okay;
}
  1. 在创建Vtable的时候 , 也会通过LinearAlloc分配VTable的内存

image.png 10.在创建interface的table的时候 , 也会通过LinearAlloc分配iftable的内存

createIftable

至此 , Class加载完成.

参考资料

Dalvik虚拟机 - 类的加载 Android类加载器 Android - Dalvik分析

0 人点赞