因爲畢業設計做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;
-------------線程同步解鎖