下面我们来说说Netty内存管理的PoolArena。之前我们说过的PoolChunk,PoolChunkList都属于一个PoolArena,通过PoolArena来管理PoolChunk和PoolChunkList。虽然提供了多个PoolArena减少线程间的竞争,但是难免还是会存在锁竞争,所以需要利用ThreaLocal进一步优化,把已申请的内存放入到ThreaLocal自然就没有竞争了。大体思路是在ThreadLocal里面放一个PoolThreadCache对象,然后释放的内存都放入到PoolThreadCache里面,下次申请先从PoolThreadCache获取。
但是,如果thread1申请了一块内存,然后传到thread2在线程释放,这个Netty在内存holder对象里面会引用PoolThreadCache,所以还是会释放到thread1里。先来看下PoolArena都有哪些属性
/**
* {@link #tinySubpagePools} 数组的大小
*
* 默认为 32 。
*/
static final int numTinySubpagePools = 512 >>> 4;
/**
* 所属 PooledByteBufAllocator 对象
*/
final PooledByteBufAllocator parent;
/**
* 满二叉树的高度。默认为 11 。
*/
private final int maxOrder;
/**
* Page 大小,默认 8KB = 8192B
*/
final int pageSize;
/**
* 从 1 开始左移到 {@link #pageSize} 的位数。默认 13 ,1 << 13 = 8192 。
*/
final int pageShifts;
/**
* Chunk 内存块占用大小。默认为 16M = 16 * 1024 。
*/
final int chunkSize;
/**
* 判断分配请求内存是否为 Tiny/Small ,即分配 Subpage 内存块。
*
* Used to determine if the requested capacity is equal to or greater than pageSize.
*/
final int subpageOverflowMask;
/**
* {@link #smallSubpagePools} 数组的大小
*
* 默认为 4
*/
final int numSmallSubpagePools;
/**
* 对齐基准
*/
final int directMemoryCacheAlignment;
/**
* {@link #directMemoryCacheAlignment} 掩码
*/
final int directMemoryCacheAlignmentMask;
/**
* tiny 类型的 PoolSubpage 数组
*
* 数组的每个元素,都是双向链表
*/
private final PoolSubpage<T>[] tinySubpagePools;
/**
* small 类型的 SubpagePools 数组
*
* 数组的每个元素,都是双向链表
*/
private final PoolSubpage<T>[] smallSubpagePools;
再来看下PoolArena是怎么分配内存的
private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
// 标准化请求分配的容量
final int normCapacity = normalizeCapacity(reqCapacity);
// PoolSubpage 的情况
if (isTinyOrSmall(normCapacity)) { // capacity < pageSize
int tableIdx;
PoolSubpage<T>[] table;
// 判断是否为 tiny 类型的内存块申请
boolean tiny = isTiny(normCapacity);
if (tiny) { // < 512 tiny 类型的内存块申请
// 从 PoolThreadCache 缓存中,分配 tiny 内存块,并初始化到 PooledByteBuf 中。
if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
// was able to allocate out of the cache so move on
return;
}
// 获得 tableIdx 和 table 属性
tableIdx = tinyIdx(normCapacity);
table = tinySubpagePools;
} else {
// 从 PoolThreadCache 缓存中,分配 small 内存块,并初始化到 PooledByteBuf 中。
if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
// was able to allocate out of the cache so move on
return;
}
// 获得 tableIdx 和 table 属性
tableIdx = smallIdx(normCapacity);
table = smallSubpagePools;
}
// 获得 PoolSubpage 链表的头节点
final PoolSubpage<T> head = table[tableIdx];
// 从 PoolSubpage 链表中,分配 Subpage 内存块
/**
* Synchronize on the head. This is needed as {@link PoolChunk#allocateSubpage(int)} and
* {@link PoolChunk#free(long)} may modify the doubly linked list as well.
*/
synchronized (head) { // 同步 head ,避免并发问题
final PoolSubpage<T> s = head.next;
if (s != head) {
assert s.doNotDestroy && s.elemSize == normCapacity;
// 分配 Subpage 内存块
long handle = s.allocate();
assert handle >= 0;
// 初始化 Subpage 内存块到 PooledByteBuf 对象中
s.chunk.initBufWithSubpage(buf, handle, reqCapacity);
// 增加 allocationsTiny 或 allocationsSmall 计数
incTinySmallAllocation(tiny);
// 返回,因为已经分配成功
return;
}
}
// 申请 Normal Page 内存块。实际上,只占用其中一块 Subpage 内存块。
synchronized (this) { // 同步 arena ,避免并发问题
allocateNormal(buf, reqCapacity, normCapacity);
}
// 增加 allocationsTiny 或 allocationsSmall 计数
incTinySmallAllocation(tiny);
// 返回,因为已经分配成功
return;
}
if (normCapacity <= chunkSize) {
// 从 PoolThreadCache 缓存中,分配 normal 内存块,并初始化到 PooledByteBuf 中。
if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
// was able to allocate out of the cache so move on
return;
}
// 申请 Normal Page 内存块
synchronized (this) { // 同步 arena ,避免并发问题
allocateNormal(buf, reqCapacity, normCapacity);
// 增加 allocationsNormal
++allocationsNormal;
}
} else {
// 申请 Huge Page 内存块
// Huge allocations are never served via the cache so just call allocateHuge
allocateHuge(buf, reqCapacity);
}
}
就是根据内存的大小,分配不同类型的内存,调用PoolSubpage或PoolChunk来分配内存。
PoolArena的分析就到这里了。
网友评论