因为毕业设计做Dalvik内存管理方面的优化。
这些天仔细阅读了下线性分配器LinearAlloc的代码
线性分配器代码为于android_src/dalvik/vm/目录下的
只有两个文件LinearAlloc.h和LinearAlloc.c代码很少,约800多行而已
线性分配器的目的在于简单、快速地分配只写一次(write-once)的内存(即分配并完成初始化写入后一般不会再改变,保持只读性质)
它主要用来管理Dalvik中类加载时的内存,因为类加载后通常是只读属性,而不需要去改变
且在程序的整个运行周期都是有效的,同时它还有共享的特性,一个应用加载后其它进程可以共享使用这些已加载的类从而加快程序的启动和运行速度
另外,在Java中动态分配内存是由堆来管理的,需要一个垃圾收集器来管理垃圾,对于永久存在的内存区不需要垃圾收集器的扫描清除,
所以将这些永久存在的内存块放到线性分配器中管理能很好地减少堆混乱和垃圾扫描,加快系性能
好吧,进入正题:
线性内存分配器用shmem从系统中申请一块大小为5M的内存,然后用自己的接口来管理它,提供分配和释放内存的API
它的线性在于分配内存从低地址到高地址,先分配的在前,后分配的在后
每个Dalvik虚拟机实例有个全局的LinearAllocHdr结构体来描述当前虚拟机的线性分配器
gDvm.pBootLoaderAlloc
以下是它的唯一一个数据结构
/*
* Linear allocation state. We could tuck this into the start of the
* allocated region, but that would prevent us from sharing the rest of
* that first page.
线性分配状态,可以将它放到分配区域的前段,但是那会防碍共享它的第一页之外的页
*/
typedef struct LinearAllocHdr {
int curOffset; /* offset where next data goes *///下一次分配的地址
pthread_mutex_t lock; /* controls updates to this struct *///用来多线程同步的锁
char* mapAddr; /* start of mmap()ed region *///分配器管理的整块内存的起始地址
int mapLength; /* length of region *///整块内存长或大小
int firstOffset; /* for chasing through *///第一次分配的位置
/* 描述内存中这些页的读写权限 它指向一个位图,位图中的每位为16bit,用来存放对应页写的次数*/
short* writeRefCount; /* for ENFORCE_READ_ONLY */
} LinearAllocHdr;
/*
* 创建一个线性分配器
*/
LinearAllocHdr* dvmLinearAllocCreate(Object* classLoader);
/*
* 销毁线性分配器
*/
void dvmLinearAllocDestroy(Object* classLoader);
/*
* 从线性分配器中分配内存
*/
void* dvmLinearAlloc(Object* classLoader, size_t size);
/*
* 释放线性分配器中的内存,注意它不会增加可用的线性内存,只是用来协助其它程序调试用
*/
void dvmLinearFree(Object* classLoader, void* mem);
还有一些其它的API,如
//重新分配大小来存储原来的内容 void* dvmLinearRealloc(Object* classLoader, void* mem, size_t newSize); //使这块区域只读 INLINE void dvmLinearReadOnly(Object* classLoader, void* mem) //全可读写 INLINE void dvmLinearReadWrite(Object* classLoader, void* mem) //同C中的strup,用来申请内存存放str中的内容,使用完后由用户负责调用释放 char* dvmLinearStrdup(Object* classLoader, const char* str); //调试用,用来打印内存中的内容 void dvmLinearAllocDump(Object* classLoader); //检查从[start,start+length)这个区域是否在线性分配的已分配区间中 bool dvmLinearAllocContains(const void* start, size_t length);
A、线性分配器的初始化
pHdr = (LinearAllocHdr*) malloc(sizeof(*pHdr));
fd = ashmem_create_region("dalvik-LinearAlloc", DEFAULT_MAX_LENGTH);//在这个地方申请了一块内存区域来存放只读数据,初始默认为5M
3.计算首次分配内存的地址
pHdr->curOffset = pHdr->firstOffset =//8-4 +4K相当于4K+4的偏移量
(BLOCK_ALIGN-HEADER_EXTRA) + SYSTEM_PAGE_SIZE;
pHdr->mapLength = DEFAULT_MAX_LENGTH;//5M
最关键的三点所以首次分配时最前面的一个页空着,并且把第二个页首地址+块对齐-头部长即4KB+8B-4B作为firstOffset和curOffset值
if (mprotect(pHdr->mapAddr + SYSTEM_PAGE_SIZE, SYSTEM_PAGE_SIZE,
ENFORCE_READ_ONLY ? PROT_READ : PROT_READ|PROT_WRITE) != 0)
{
5.计算页数量,并为位图申请空间
int numPages = (pHdr->mapLength+SYSTEM_PAGE_SIZE-1) / SYSTEM_PAGE_SIZE;
pHdr->writeRefCount = calloc(numPages, sizeof(short));//每一个页用16b来表示总共为numPages*16bit的位图空间
dvmInitMutex(&pHdr->lock);
7.返回描述符
return pHdr;
初始状态如下图
LinearAllocHdr* pHdr = getHeader(classLoader);
原型如下
static inline LinearAllocHdr* getHeader(Object* classLoader)//输入参数无用
{
return gDvm.pBootLoaderAlloc;//这个是一个全局的,总的线性分配器状态
}
2.计算下一次分配的地址,即curOffset值
startOffset = pHdr->curOffset;//即当前分配地址的起始
nextOffset = ((startOffset + HEADER_EXTRA*2 + size + (BLOCK_ALIGN-1))
& ~(BLOCK_ALIGN-1)) - HEADER_EXTRA;
这个计算式很重要由于上面三点的原因中的
nextOffset = ((startOffset + HEADER_EXTRA*2 + size + (BLOCK_ALIGN-1))& ~(BLOCK_ALIGN-1))//这个处理对齐,两个HEAD是为了给下一个块头预留空间
nextOffset -= HEADER_EXTRA;//分配位置对齐后向前减去一个头部的长度(亦即用掉上面预留的头部空间)
size = nextOffset - (startOffset + HEADER_EXTRA);//少4B,因为头部4字节
lastGoodOff = (startOffset-1) & ~(SYSTEM_PAGE_SIZE-1);//用公式(n,n+1]->n计算它的页序即第几个页
firstWriteOff = startOffset & ~(SYSTEM_PAGE_SIZE-1);//用公式[n,n+1)->n计算页序
/*上果上述两者不相等,则表示它跨页了?,但是也仅仅表示恰好在页对齐处, 而且此时lastGoodOff为上一个页数*/
lastWriteOff = (nextOffset-1) & ~(SYSTEM_PAGE_SIZE-1);//-1限定了只能在页边界处才能出现,lastGoodOff!=lastWriteOff只出现在lastGoodOff恰好是页边界时
if (lastGoodOff != lastWriteOff || ENFORCE_READ_ONLY) {
//代码为跨页处理权限问题
}
f (lastGoodOff != lastWriteOff || ENFORCE_READ_ONLY) {
int cc, start, len;
start = firstWriteOff;
assert(start <= nextOffset);
len = (lastWriteOff - firstWriteOff) + SYSTEM_PAGE_SIZE;
cc = mprotect(pHdr->mapAddr + start, len, PROT_READ | PROT_WRITE);//设为可读写
}
*(u4*)(pHdr->mapAddr + startOffset) = size;
pHdr->curOffset = nextOffset;//写入计算得到的分配地址
6.返回此次分配的地址
return pHdr->mapAddr + startOffset + HEADER_EXTRA;
-------------线程同步解锁