毕业前夕提升系列(二):Netty总结(6)——subPage的分配
前言:之前是大于一个pageSize(8k)则在一个PoolChunk(6M)里进行分配,
前面逻辑还是跟page级别内存分配类似:
内存规格化,将需要分配的内存进行两倍化(page、subPage级别规格)
首先是从cache上分配
如果cache上无法分配的话,从poolChunkList上分配
- 若chunk为null(首次), chunk(初始化depth、memory数组、PoolSubpage数组)
PoolSubpage数组
在PoolArena
初始化PoolSubpage数组
1 2 3 4 5
| static final int numTinySubpagePools = 512 >>> 4; tinySubpagePools = newSubpagePoolArray(numTinySubpagePools); for (int i = 0; i < tinySubpagePools.length; i ++) { tinySubpagePools[i] = newSubpagePoolHead(pageSize); }
|
这里初始化的是头部节点,会创建出32个PoolSubpage,(0B、16B、32B…)都是16的倍数(最小是16字节),数组每一个head节点后面是链接的是具体分配相对应的subPage

subPage分配:allocateTiny
- 定位一个subPage对象
- 初始化subPage
- PooledByteBuf初始化,拿到内存信息给pooledByteBuf进行初始化
核心分配逻辑PoolChunk#allocateSubpage()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| private long allocateSubpage(int normCapacity) {
PoolSubpage<T> head = arena.findSubpagePoolHead(normCapacity);
synchronized (head) {
int d = maxOrder;
int id = allocateNode(d);
if (id < 0) {
return id;
}
final PoolSubpage<T>[] subpages = this.subpages; final int pageSize = this.pageSize;
freeBytes -= pageSize;
int subpageIdx = subpageIdx(id);
PoolSubpage<T> subpage = subpages[subpageIdx];
if (subpage == null) {
subpage = new PoolSubpage<T>(head, this, id, runOffset(id), pageSize, normCapacity); subpages[subpageIdx] = subpage;
} else {
subpage.init(head, normCapacity);
}
return subpage.allocate();
}
}
|
1. PoolSubpage的构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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]; init(head, elemSize); }
void init(PoolSubpage<T> head, int elemSize) { doNotDestroy = true; this.elemSize = elemSize; if (elemSize != 0) { maxNumElems = numAvail = pageSize / elemSize; nextAvail = 0; bitmapLength = maxNumElems >>> 6; if ((maxNumElems & 63) != 0) { bitmapLength ++; }
for (int i = 0; i < bitmapLength; i ++) { bitmap[i] = 0; } } addToPool(head); }
|
- 每个chunk都会维护一个subpage列表(数量2048),存放page被切分后的子page
- 通过bimap来维护哪一个index的subpage被分配,0表示未分配,1表示已分配
- element代表subpage更小的单位,elementSize则表示一个element的大小,是外部传进的规格化内存分配大小(16byte*2的n次方)
- pageSize为8k,
bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
,16是基础字节大小,page中element的数量是不会多于 pageSize/16个的,每个long有64位,每位便可标识一个element的使用情况。
- init代码:例如分配16byte的空间,将page/elemSize就是把当前page切分成多少个子page(elements),bitmap是long数组,所以再除以64,就是每一个bit位来表示64个子page的分配情况。(例如分配16byte,则当前page就有512个elements)
addToPool(head)
将当前创建的节点连接到head节点后
注:这里每个chunk(page级别)有一个subPage[2048]、之前有Arean内也有subPage[32],区别?
实际上两者之间的关系可以用下面的数据结构来展示:

整个Chunk二叉树的最底层才可以分配,0~8k为第0个subPage、依次类推存入Chunk里的subpages数组里;而Arean的head是空的,来方便找寻后面链表中合适的subPage进行分配(找16B的则找idx为0的head节点进行分配)
2. 从poolSubPage对象中分配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| long allocate() { if (elemSize == 0) { return toHandle(0); }
if (numAvail == 0 || !doNotDestroy) { return -1; }
final int bitmapIdx = getNextAvail(); int q = bitmapIdx >>> 6; int r = bitmapIdx & 63; assert (bitmap[q] >>> r & 1) == 0; bitmap[q] |= 1L << r;
if (-- numAvail == 0) { removeFromPool(); }
return toHandle(bitmapIdx); }
|
2.1 拿到合适的可用的bitMapIdx:getNextAvail()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| private int getNextAvail() { int nextAvail = this.nextAvail; if (nextAvail >= 0) { this.nextAvail = -1; return nextAvail; } return findNextAvail(); }
private int findNextAvail() { final long[] bitmap = this.bitmap; final int bitmapLength = this.bitmapLength; for (int i = 0; i < bitmapLength; i ++) { long bits = bitmap[i]; if (~bits != 0) { return findNextAvail0(i, bits); } } return -1; }
private int findNextAvail0(int i, long bits) { final int maxNumElems = this.maxNumElems; final int baseVal = i << 6;
for (int j = 0; j < 64; j ++) { if ((bits & 1) == 0) { int val = baseVal | j; if (val < maxNumElems) { return val; } else { break; } } bits >>>= 1; } return -1; }
|
2.2 toHandle(bitmapIdx)
//todo
1 2 3
| private long toHandle(int bitmapIdx) { return 0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx; }
|
handle指向的是当前chunk中的唯一的一块内存(哪一个chunk的哪一个page),通过这个long,就可以找到对应的chunk,subpage以及element的位置信息
handle的组成信息是由高32位bitmapIdex和低32位memoryMapIdx组成
buf的初始化
chunk上根据前面拿到的handle进行buf的初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| c.initBuf(buf, handle, reqCapacity);
void initBuf(PooledByteBuf<T> buf, long handle, int reqCapacity) { int memoryMapIdx = memoryMapIdx(handle); int bitmapIdx = bitmapIdx(handle); if (bitmapIdx == 0) { byte val = value(memoryMapIdx); assert val == unusable : String.valueOf(val); buf.init(this, handle, runOffset(memoryMapIdx) + offset, reqCapacity, runLength(memoryMapIdx), arena.parent.threadCache()); } else { initBufWithSubpage(buf, handle, bitmapIdx, reqCapacity); } }
private void initBufWithSubpage(PooledByteBuf<T> buf, long handle, int bitmapIdx, int reqCapacity) { assert bitmapIdx != 0;
int memoryMapIdx = memoryMapIdx(handle);
PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)]; assert subpage.doNotDestroy; assert reqCapacity <= subpage.elemSize;
buf.init( this, handle, runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize + offset, reqCapacity, subpage.elemSize, arena.parent.threadCache()); }
|
注:runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize + offset
page偏移量加上element的bit位置的偏移量*一个element的大小加上PoolChunk的地址就是buf的地址
参考
Netty之SubPage级别的内存分配
五、netty的内存管理