Android 2.3中的LinearAlloc

原因

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
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
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

  2. 在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
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
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
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

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分析

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容