关于Netty内存池管理已经介绍了PoolChunk以及PoolSubPage来管理内存池。然后从应用层调用来看,应用层序并未直接去调用PoolChunk或PoolSubPage的allocate/free方法去管理内存,当我们通过PooledByteBufAllocator去分配操作内存时,它将内存的管理职责委托给PoolArea,本文将介绍PoolArea是如何管理内存的。
其成员变量如下:
static final int numTinySubpagePools = 512 >>> 4; //tiny缓存数量
final PooledByteBufAllocator parent; //分配器
private final int maxOrder; //平衡二叉树最大层数
final int pageSize; //pageSize大小
final int pageShifts; //pageSize
final int chunkSize; //chunk块大小
final int subpageOverflowMask; //判断是否<8k
final int numSmallSubpagePools; //small subpage缓存数量
private final PoolSubpage<T>[] tinySubpagePools; //tiny subpage缓存数组
private final PoolSubpage<T>[] smallSubpagePools; //small subpage缓存数组
private final PoolChunkList<T> q050; //基于PoolChunk链表
private final PoolChunkList<T> q025;
private final PoolChunkList<T> q000;
private final PoolChunkList<T> qInit;
private final PoolChunkList<T> q075;
private final PoolChunkList<T> q100;
在PoolArea成员变量中,包含基于PoolChunk的链表,以及PoolSubPage的数组。此外,在PoolArea中对PoolSubPage内存的分配又分为两类
1)>512byte 对应smallSubpagePools
2)<512byte 对应tinySubPagePools
protected PoolArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
this.parent = parent;
this.pageSize = pageSize;
this.maxOrder = maxOrder;
this.pageShifts = pageShifts;
this.chunkSize = chunkSize;
subpageOverflowMask = ~(pageSize - 1);
tinySubpagePools = newSubpagePoolArray(numTinySubpagePools);
for (int i = 0; i < tinySubpagePools.length; i ++) {
tinySubpagePools[i] = newSubpagePoolHead(pageSize);
}
numSmallSubpagePools = pageShifts - 9;
smallSubpagePools = newSubpagePoolArray(numSmallSubpagePools);
for (int i = 0; i < smallSubpagePools.length; i ++) {
smallSubpagePools[i] = newSubpagePoolHead(pageSize);
}
q100 = new PoolChunkList<T>(this, null, 100, Integer.MAX_VALUE);
q075 = new PoolChunkList<T>(this, q100, 75, 100);
q050 = new PoolChunkList<T>(this, q075, 50, 100);
q025 = new PoolChunkList<T>(this, q050, 25, 75);
q000 = new PoolChunkList<T>(this, q025, 1, 50);
qInit = new PoolChunkList<T>(this, q000, Integer.MIN_VALUE, 25);
q100.prevList = q075;
q075.prevList = q050;
q050.prevList = q025;
q025.prevList = q000;
q000.prevList = null;
qInit.prevList = qInit;
}
在构造器中,初始化tinySubpagePool与smallSubpagePool数组,以及q100等5个PoolChunkList,并将这5个PoolChunkList在连接为链表,如下图所示:
1)qInit:存储内存使用率0-25%的chunk
2)q000:存储内存使用率1-50%的chunk
3)q025:存储内存使用率25-75%的chunk
4)q050:存储内存使用率50-100%个chunk
5)q075:存储内存使用率75-100%个chunk
6)q100:存储内存使用率100%chunk
构造器初始化完之后,接下来关注PoolArea的分配方法
PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
PooledByteBuf<T> buf = newByteBuf(maxCapacity);
allocate(cache, buf, reqCapacity);
return buf;
}
在上面的分配方法中,newByeteBuf(int)为抽象方法,由子类(基于directArea以及heapArea的PoolArea)具体实现,接着进入内存标记管理环节
private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
final int normCapacity = normalizeCapacity(reqCapacity); //内存对齐
if (isTinyOrSmall(normCapacity)) { // capacity < pageSize
int tableIdx;
PoolSubpage<T>[] table;
if (isTiny(normCapacity)) { // < 512 byte
if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) { //从线程缓存分配
return;
}
tableIdx = tinyIdx(normCapacity);
table = tinySubpagePools;
} else { // > 512byte && < 8K
if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) { //从线程缓存分配
return;
}
tableIdx = smallIdx(normCapacity);
table = smallSubpagePools;
}
synchronized (this) { //从PoolSubPage数组分配
final PoolSubpage<T> head = table[tableIdx];
final PoolSubpage<T> s = head.next;
if (s != head) {
assert s.doNotDestroy && s.elemSize == normCapacity;
long handle = s.allocate();
assert handle >= 0;
s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
return;
}
}
} else if (normCapacity <= chunkSize) { // < chunkSize
if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) { //从线程缓存分配
return;
}
} else { // > chunkSize
allocateHuge(buf, reqCapacity); //直接分配
return;
}
allocateNormal(buf, reqCapacity, normCapacity); //从平衡二叉树分配
}
在申请内存时,经过内存对齐算法计算之后申请内存的大小特点如下:
1)当申请大小>chunkSize,实际申请大小等于初始申请大小
2)当申请大小>512byte,实际申请大小等于512的整数倍中最小而又大于或等于申请大小的值,如申请大小513 对应1024、4010对应4096
3)当申请大小小于<512byte时,申请大小为16的整数倍中最小而又大于或等于申请大小的值,如申请7对应16,55对应64;
在进行内存对齐之后,PoolArea将内存的申请划分为4类;
1) <512byte,由tinyPoolSubPage相应资源分配
2) 大于等于512byte且小于pageSize,由smallPoolSubPage相应资源分配
3) 大于等于pageSize且小于等于chunkSize,由PoolChunk在平衡二叉树上分配
4) >chunkSize,直接分配不缓存
同时,在PoolSubPage(smart和tiny两种)时分配时的原则大致如下:在对应线程缓存分配,成功直接返回,失败则在对应数组缓存数组分配。而PoolChunk时分配时,原则类似,先在线程缓存分配,成功立即返回,失败则Chunk维护的平衡二叉树上进行分配。此外当从PoolSubPage数组中分配失败是,将从PoolChunk平衡二叉树上分配。关于本地线程缓存分配,之后文章PoolThradCache将会详细分析。
PoolSubPage数组的分配委托给PoolChunk中的initBufWithSubPage分配,而PoolChunk上平衡而二叉树的分配如下方法:
private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
q075.allocate(buf, reqCapacity, normCapacity) || q100.allocate(buf, reqCapacity, normCapacity)) {
return;
}
PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
long handle = c.allocate(normCapacity);
assert handle > 0;
c.initBuf(buf, handle, reqCapacity);
qInit.add(c);
}
在其分配过程,首先会尝试去构造器初始化PoolChunkList链表中分配,其分配顺序顺序为q050->q025->q000->qInit->q075->q100中分配,其分配顺序基于以上顺序原因:
1)优先在内存利用率50%-100%的内存块分配应为该区域内存利用率高,提高系统内存利用率;
2)最低优先级在75%及以上内存块分配,以提高内存分配成功率
初始分配时,PoolChunkList无法分配,会直接基于PoolChunk进行分配,并申请到的chunk加入qInit中。当PoolChunk进入PoolChunkList之后,会根据当前chunk内存利用率和当前所在PoolChunkList指定的利用情况,可能进行移动。例如在q050中的chunk块,内存使用率超过75%时,该chunk加从q050转移至q075,内存使用率效益25%时,将由q050转移至q025
关于PoolArea如何释放内存方法如下:
void free(PoolChunk<T> chunk, long handle, int normCapacity, boolean sameThreads) {
if (chunk.unpooled) { //chunk未缓存,直接回收
destroyChunk(chunk);
} else {
if (sameThreads) { //在同一线程中,将分配内存添加至线程缓存
PoolThreadCache cache = parent.threadCache.get();
if (cache.add(this, chunk, handle, normCapacity)) {
// cached so not free it.
return;
}
}
synchronized (this) { //不在同一线程
chunk.parent.free(chunk, handle); //PoolChunkList负责释放
}
}
}
至此,PoolArea内存管理流程大致介绍完毕。而PoolArea作为抽象内定义的一些列抽象方法,由内部类DirectArea(直接内存)与HeapArea(堆内存)完成特定实现。