(*文章基于Netty4.1.22版本)
整體介紹
在Netty的內(nèi)存分配的整體架構(gòu)中,按我的理解,PoolChunkList是為了解決隨著分配的次數(shù)增加儡循,分配一段連續(xù)內(nèi)存失敗率提高的問(wèn)題隔披。
試想一下在Chunk中我們分配了很多小的內(nèi)存枢冤,16B鸠姨,32B,把整個(gè)Chunk都切割成這種小的內(nèi)存塊淹真,那么當(dāng)我們想要分配8KB或者更大的內(nèi)存的時(shí)候讶迁,就會(huì)失敗,因?yàn)槠渌鸆hunk被切割成很小的內(nèi)存碎片核蘸,無(wú)法分配很大的一段內(nèi)存巍糯,所以PoolChunkList的結(jié)構(gòu)就出現(xiàn)了,將不同使用率的PoolChunk分隔開客扎,增加了分配大的連續(xù)段成功的幾率祟峦。
PoolChunkList是一個(gè)鏈表,結(jié)構(gòu)如下:
在整個(gè)PoolArena中徙鱼,共有6個(gè)PoolChunkList宅楞,使用率越大的在鏈表的后面,按照不同的使用率疆偿,命名如下:
- qInit:使用率 0 ~ 25
- q000:使用率 1 ~ 50
- q025:使用率 25 ~ 75
- q050:使用率 50 ~ 100
- q075:使用率 75 ~ 100
- q100:使用率 100 ~ 100
源碼分析
變量介紹
private final PoolArena<T> arena;//該 Chunk所屬的Arena
private final PoolChunkList<T> nextList;// 下一個(gè)使用率的ChunkList
private final int minUsage;// 最小使用率
private final int maxUsage;// 最小使用率
private final int maxCapacity;// 最大的容量
private PoolChunk<T> head;// ChunkList中的頭節(jié)點(diǎn)
初始化
PoolChunkList(PoolArena<T> arena, PoolChunkList<T> nextList, int minUsage, int maxUsage, int chunkSize) {
assert minUsage <= maxUsage;
this.arena = arena;
this.nextList = nextList;
this.minUsage = minUsage;
this.maxUsage = maxUsage;
maxCapacity = calculateMaxCapacity(minUsage, chunkSize);
}
簡(jiǎn)單的賦值操作咱筛,重點(diǎn)看下calculateMaxCapacity方法
private static int calculateMaxCapacity(int minUsage, int chunkSize) {
minUsage = minUsage0(minUsage);//max(1, minUsage)
if (minUsage == 100) {// 如果最小使用率都百分百了,自然最大可用空間為0
return 0;
}
return (int) (chunkSize * (100L - minUsage) / 100L);
}
分配
boolean allocate(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
// 如果當(dāng)前ChunkList有Chunk可以分配 或者 請(qǐng)求的大小符合ChunkList的范圍
if (head == null || normCapacity > maxCapacity) {
return false;
}
// 遍歷ChunkList中的Chunk進(jìn)行分配
for (PoolChunk<T> cur = head;;) {
long handle = cur.allocate(normCapacity);
if (handle < 0) {// 分配失敗杆故,找下一個(gè)
cur = cur.next;
if (cur == null) {
return false;
}
} else {
cur.initBuf(buf, handle, reqCapacity);// 初始化buf,將buf和Chunk關(guān)聯(lián)
if (cur.usage() >= maxUsage) {// 分配好之后溉愁,如果大于當(dāng)前ChunkList处铛,那么移動(dòng)到下一個(gè)ChunkList
remove(cur);
nextList.add(cur);
}
return true;
}
}
}
添加Chunk
當(dāng)一個(gè)Chunk要進(jìn)入ChunkList,要調(diào)用add方法
void add(PoolChunk<T> chunk) {
// 如果說(shuō)加入的這個(gè)chunk的使用率大于當(dāng)前ChunkList最大的使用率拐揭,那么肯定不能加入當(dāng)前的ChunkList
// 要加入下一個(gè)ChunkList中撤蟆,直到找到合適的ChunkList
if (chunk.usage() >= maxUsage) {
nextList.add(chunk);
return;
}
add0(chunk);
}
// 設(shè)置Chunk所屬的ChunkList,并加入到head前面堂污,并將該Chunk設(shè)置為新的head
void add0(PoolChunk<T> chunk) {
chunk.parent = this;
if (head == null) {
head = chunk;
chunk.prev = null;
chunk.next = null;
} else {
chunk.prev = null;
chunk.next = head;
head.prev = chunk;
head = chunk;
}
}
釋放Chunk
當(dāng)從Chunk分配的內(nèi)存使用完后家肯,在調(diào)用Chunk的free方法釋放內(nèi)存之后,這個(gè)Chunk的使用率會(huì)發(fā)生變化盟猖,可能需要移動(dòng)該Chunk
boolean free(PoolChunk<T> chunk, long handle) {
chunk.free(handle);
// //如果釋放之后讨衣,使用率低于該ChunkList的最低使用率,則從該ChunkList中移除式镐,添加到前面的ChunkList
if (chunk.usage() < minUsage) {
remove(chunk);
// 在鏈表上尋找使用率相符的ChunkList添加
return move0(chunk);
}
return true;
}