Netty16# 池化內(nèi)存Subpage類型內(nèi)存分配
前言
前面聊了大于8KB的內(nèi)存分配,那小于8KB的呢?上一篇的平衡二叉樹第十一層的葉子節(jié)點(diǎn)最小也是8KB,那比如要分配128B的緩存,直接分給8KB顯然是不合適的,Tiny是小于512Byte,Small介于512B~8KB,Tiny和Small統(tǒng)稱Subpage,本文就聊聊他們的內(nèi)存分配情況,這塊應(yīng)該是整個(gè)netty最為復(fù)雜的部分了。
內(nèi)容提要
下面是以分配128B為例的整體流程架構(gòu)圖,下面大體敘述下其流程。
先從平衡二叉樹的第11層選一個(gè)未分配的葉子節(jié)點(diǎn)大小為8KB的一個(gè)Page
備注:本例中為memoryMap[2048]對該P(yáng)age進(jìn)行切割,假如要分配128B,整體會切割為64塊
備注:8192/128=64通過long類型二進(jìn)制64位來標(biāo)記分割成各個(gè)塊的分配狀態(tài)
備注:0:未分配,1:已分配
一個(gè)bitmap數(shù)組長度為8,每個(gè)元素都能對64塊內(nèi)存進(jìn)行標(biāo)記
建立了二叉樹節(jié)點(diǎn)與切分塊之間的映射關(guān)系
備注:memoryMapIdx ^ maxSubpageAllocs分配后建立二叉樹葉子節(jié)點(diǎn)與標(biāo)記位之間的關(guān)系,可以指向內(nèi)存一塊區(qū)域
備注:0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx

源碼分析
示例代碼
@Test
public void testAllocateSubpage() {
ByteBufAllocator allocator = new PooledByteBufAllocator();
allocator.directBuffer(128);
}
備注:以分配128B的內(nèi)存為例,分析其分配過程。
源碼分析
private long allocateSubpage(int normCapacity) {
PoolSubpage<T> head = arena.findSubpagePoolHead(normCapacity); // 注解@1
int d = maxOrder;
synchronized (head) {
int id = allocateNode(d); // 注解@2
if (id < 0) {
return id;
}
final PoolSubpage<T>[] subpages = this.subpages; // 注解@3
final int pageSize = this.pageSize;
freeBytes -= pageSize;
int subpageIdx = subpageIdx(id); // 注解@4
PoolSubpage<T> subpage = subpages[subpageIdx];
if (subpage == null) { // 注解@5
subpage = new PoolSubpage<T>(head, this, id, runOffset(id), pageSize, normCapacity);
subpages[subpageIdx] = subpage;
} else {
subpage.init(head, normCapacity);
}
return subpage.allocate(); // 注解@6
}
}
注解@1 從tinySubpagePools中獲取PoolSubpage。獲取過程為elemSize >>> 4(除以16)來獲取。

tinySubpagePools結(jié)構(gòu)
tinySubpagePools被初始化成長度為32的數(shù)組,元素之間差額為16B。

注解@2 allocateNode 在上一篇文章分析過,d = maxOrder = 1。表示在平衡二叉樹的第11層找到可分配的節(jié)點(diǎn),具體為memoryMap數(shù)組中的下標(biāo)。如果整個(gè)樹都沒有內(nèi)存可分配了,返回的id=-1。
注解@3 先看下subpages的初始化,maxSubpageAllocs = 1 << maxOrder= 2048。也就是PoolSubpage
subpages = newSubpageArray(maxSubpageAllocs);
注解@4 將平衡二叉樹第11層的下標(biāo)memoryMap[]的下標(biāo)轉(zhuǎn)換為subpages[]數(shù)組的下標(biāo)。轉(zhuǎn)換關(guān)系為memoryMapIdx ^ maxSubpageAllocs。
例如:平衡二叉樹第11層第1個(gè)節(jié)點(diǎn)數(shù)組下標(biāo)為2048,轉(zhuǎn)換為subpages的下標(biāo)為0,平衡二叉樹第11層第2個(gè)節(jié)點(diǎn)數(shù)組下標(biāo)為2049,轉(zhuǎn)換為subpages的下標(biāo)為1,平衡二叉樹第11層第2個(gè)節(jié)點(diǎn)數(shù)組下標(biāo)為2050,轉(zhuǎn)換為subpages的下標(biāo)為2。

注解@5 初始化PoolSubpage
PoolSubpage(PoolSubpage<T> head, PoolChunk<T> chunk, int memoryMapIdx, int runOffset, int pageSize, int elemSize) {
this.chunk = chunk;
this.memoryMapIdx = memoryMapIdx;
this.runOffset = runOffset;
this.pageSize = pageSize;
bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
init(head, elemSize);
}
參數(shù)說明
head: PoolSubpage數(shù)組中的一個(gè)元素,本例中為第4個(gè)元素
chunk: 當(dāng)前PoolChunk實(shí)例
memoryMapIdx: 平衡二叉樹第11層用于分配的節(jié)點(diǎn),具體為memoryMap數(shù)組下標(biāo)
elemSize: 待分配的內(nèi)存,本例中為128KB
bitmap: long數(shù)組長度為8「8192無符號右移10位=8」
初始化說明
void init(PoolSubpage<T> head, int elemSize) {
doNotDestroy = true;
// 待分配內(nèi)存
this.elemSize = elemSize;
if (elemSize != 0) {
// maxNumElems表示可以被切割成幾份(8192除以待分配內(nèi)存)例如:64=8192/128被切成了64份
maxNumElems = numAvail = pageSize / elemSize;
nextAvail = 0;
// 無符號右移6位,高位補(bǔ)零(相當(dāng)于除以64)例如:64的二進(jìn)制右移6位為1,128的二進(jìn)制右移6位為2
bitmapLength = maxNumElems >>> 6;
if ((maxNumElems & 63) != 0) { // 相當(dāng)于是否能被64整除
bitmapLength ++; // 不能被整除遞增bitmapLength
}
for (int i = 0; i < bitmapLength; i ++) {
bitmap[i] = 0; // 等于零表示未被分配
}
}
addToPool(head);
}
過程說明
@1 先計(jì)算一個(gè)Page被切成了幾份 maxNumElems( pageSize / elemSize)
@2 計(jì)算bitmap數(shù)組長度bitmapLength(maxNumElems無符號右移6位相當(dāng)于除以64)
備注: 此處不太好理解為什么要maxNumElems要除以64來計(jì)算bitmap的長度呢?也就是bitmap數(shù)組中的每個(gè)元素可以標(biāo)記64個(gè)被切的內(nèi)存塊。bitmap是long數(shù)組,每個(gè)long類型是64位,他用每個(gè)二進(jìn)制位來標(biāo)記被切內(nèi)存塊的分配情況。

加入鏈表
新構(gòu)建的PoolSubpage與tinySubpagePools中的PoolSubpage建成鏈表關(guān)系。
private void addToPool(PoolSubpage<T> head) {
assert prev == null && next == null;
prev = head;
next = head.next;
next.prev = this;
head.next = this;
}
小結(jié): 構(gòu)造的PoolSubpage中持有了一個(gè)bitmap[]數(shù)組,數(shù)組長度與待分配的內(nèi)存有關(guān)。待分配內(nèi)存大小為elemSize,數(shù)組長度=PageSize/elemSize,并將bitmap數(shù)組的元素標(biāo)記為未分配。

注解@6 分配內(nèi)存
內(nèi)存的分配以兩次分配128B內(nèi)存為例觀察期分配過程。
@Test
public void testAllocateSubpage() {
ByteBufAllocator allocator = new PooledByteBufAllocator();
allocator.directBuffer(128); // 第一次分配
allocator.directBuffer(128); // 第二次分配
}
第一次分配


第二次分配

第一次輪詢第一位已被占用,需要向右移位。

第二次輪詢第二位未被占用。

第二次分配過程

兩次內(nèi)存分配圖示
第一次分配128B圖示
此時(shí)64位第一位被標(biāo)記為1,bitmap[0] = 1

第二次分配128B圖示
此時(shí)64位第二位也被標(biāo)記為1,bitmap[0] = 3

