原因
在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
解決方案
- 在打MultiDex的時候 , 添加dx的參數
--ser-max-idx-number=48000
, 讓每個Dex的最大方法數最大為48000 , 避免出現LinearAlloc
dexOptions {
javaMaxHeapSize "2g"
additionalParameters += '--multi-dex'
// 設置Dex的最大方法數
additionalParameters += '--set-max-idx-number=62000'
}
- 配置Proguard , 優化代碼 , 減少方法和類數量.
- Dex過多會導致2.x的版本 , 可能會出現ANR的問題 , 可以通過多進程Dexopt來處理該問題.
流程
-
以2.3版本為例LinearAlloc最大內存為 : 5M
image.png
在調用dvmLinearAllocCreate
函數中 , 會通過ashmem_create_region
創建一片5M大小的內存空間
image.png
- 在
class.cc
中 , 會調用dvmClassStartup
函數創建LinearAllocHdr對象 , 并且賦值給gDvm.pBootLoaderAlloc用于后續的dex加載
image.png
-
在
LinearAlloc.cc
中調用dvmLinearAlloc
為classLoader分配大小
image.png 在ClassLoader將Odex文件加載入內存后, 校驗Odex的CRC32、簽名、Magic等 , 并且構建DexFile結構用于標識Method、Class等方法入口.
當使用到Class時 , 將會調用findClass
來加載類文件 , 同時使用LinearAlloc為interface、Method分配內存.
ClassLoder defineClass借圖
-
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;
}
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;
}
}
- 在
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;
}
- 在創建Vtable的時候 , 也會通過
LinearAlloc
分配VTable的內存
image.png
10.在創建interface的table的時候 , 也會通過LinearAlloc
分配iftable的內存
createIftable
至此 , Class加載完成.