原因
在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加载完成.
参考资料
Dalvik虚拟机 - 类的加载 Android类加载器 Android - Dalvik分析