PoolChunk使用了jemalloc分配算法。对这个算法不了解的,请另行百度哈,这里我就不展开了。
首先说下几个概念吧。page是chunk中内存分配的最小单元,chunk是由一系列的page组成的。当然,page也可以分割成一系列的subpage。一个chunk的大小chunksize=2{maxorder}*pageSize。
PoolChunk是由final修饰的,这代表不能去修改的哈。
先看下PoolChunk的成员变量咯
final PoolArena<T> arena;//chunk所属的arena
final T memory;//拥有的内存块
final boolean unpooled;//是否非池化
final int offset;//偏移量
private final byte[] memoryMap;//各个page分配的二叉树
private final byte[] depthMap;//高度二叉树
private final PoolSubpage<T>[] subpages;//subpage的节点数组
/** Used to determine if the requested capacity is equal to or greater than pageSize. */
private final int subpageOverflowMask;//判断分配的请求是subpage
private final int pageSize;//页大小 默认为8K
private final int pageShifts;//1左移多少位 == pageSize 默认为13 即 1 << 13 =8k
private final int maxOrder;//最大的高度 默认 11
private final int chunkSize;//块大小 默认 16MB
private final int log2ChunkSize;//log2(chunkSize) 默认24
private final int maxSubpageAllocs;//可分配的最大节点数 默认 1<<11= 2048
/** Used to mark memory as unusable */
private final byte unusable;//标记节点为不可用 maxOrder +1 默认12
private int freeBytes;//可分配的字节数
PoolChunkList<T> parent;//poolChunkList使用,双向链表
PoolChunk<T> prev;
PoolChunk<T> next;
该类有两个构造函数,一个用于普通初始化,另一个用于Huge分配请求。
我们这就只关注普通初始化,上代码
PoolChunk(PoolArena<T> arena, T memory, int pageSize, int maxOrder, int pageShifts, int chunkSize, int offset) {
unpooled = false;
this.arena = arena;
this.memory = memory;
this.pageSize = pageSize;
this.pageShifts = pageShifts;
this.maxOrder = maxOrder;
this.chunkSize = chunkSize;
this.offset = offset;
unusable = (byte) (maxOrder + 1);//不可用标记
log2ChunkSize = log2(chunkSize);
subpageOverflowMask = ~(pageSize - 1);//判断是否是subpage请求的标志
freeBytes = chunkSize;//可分配的字节数
assert maxOrder < 30 : "maxOrder should be < 30, but is: " + maxOrder;
maxSubpageAllocs = 1 << maxOrder;//subpage最大分配个数
//初始化数据
// Generate the memory map.
memoryMap = new byte[maxSubpageAllocs << 1];
depthMap = new byte[memoryMap.length];
int memoryMapIndex = 1;
for (int d = 0; d <= maxOrder; ++ d) { // move down the tree one level at a time
int depth = 1 << d;
for (int p = 0; p < depth; ++ p) {
// in each level traverse left to right and set value to the depth of subtree
memoryMap[memoryMapIndex] = (byte) d;
depthMap[memoryMapIndex] = (byte) d;
memoryMapIndex ++;
}
}
subpages = newSubpageArray(maxSubpageAllocs);
}
构造方法没什么好看的,无非就是给各个属性赋值,在给memoryMap 和depthMap初始化各个值。
下面看下一个核心的方法long allocate(int normCapacity)
//首先判断要分配的空间大小是否 >= pageSize
if ((normCapacity & subpageOverflowMask) != 0) {
return allocateRun(normCapacity);//走正常分配流程
} else {
return allocateSubpage(normCapacity);//分配subpage
}
这个方法疑惑的地方应该就是(normCapacity & subpageOverflowMask) != 0这个判断了。因为netty追求性能,所以把位运算算是运用到了极致。之前再构造方法里看了 subpageOverflowMask = ~(pageSize-1),按默认的来说 即subpageOverflowMask = ~(8192-1) = -8972;而-8192 & 小于 8972的数都是为0 的,缺少画图工具,图就不划了,可以自己敲代码验证一下。
long allocateRun(int normCapacity)
//该方法至少会分配一个pageSize的内存
private long allocateRun(int normCapacity) {
//首先计算出所需要的节点的高度
int d = maxOrder - (log2(normCapacity) - pageShifts);
//在该层找到能分配的节点
int id = allocateNode(d);
//没找到
if (id < 0) {
return id;
}
//计算剩余能分配的字节数
freeBytes -= runLength(id);
return id;
}
private int allocateNode(int d)
private int allocateNode(int d) {
int id = 1;
//和subpageOverflowMask作用相当,所有小于 d 的值 &initial 都等于0
int initial = - (1 << d);
//memoryMap[id]
byte val = value(id);
//表示没有满足条件的节点
if (val > d) { // unusable
return -1;
}
//子节点可满足田间
while (val < d || (id & initial) == 0) { // id & initial == 1 << d for all ids at depth d, for < d it is 0
//左移一位,进入子节点
id <<= 1;
val = value(id);
if (val > d) {//左节点不满足
id ^= 1;//右节点
val = value(id);
}
}
byte value = value(id);
assert value == d && (id & initial) == 1 << d : String.format("val = %d, id & initial = %d, d = %d",
value, id & initial, d);
//把符合条件的节点标记位不可用 memoryMap[id] = unusable;
setValue(id, unusable); // mark as unusable
//更新父节点的分配信息
updateParentsAlloc(id);
return id;
}
private void updateParentsAlloc(int id)
while (id > 1) {
//取父节点
int parentId = id >>> 1;
byte val1 = value(id);//获取该节点的值
byte val2 = value(id ^ 1);//获取该节点的兄弟节点的值
byte val = val1 < val2 ? val1 : val2;//获取较小的值
setValue(parentId, val);//把较小的值付给父节点
id = parentId;//递归更新
}
下面分析分配的空间 <pageSize的情况
allocateSubpage(normCapacity)
//找到subpage的头节点
PoolSubpage<T> head = arena.findSubpagePoolHead(normCapacity);
//加锁,因为在分配的过程中会修改链表的结构
synchronized (head) {
//subpage只能在最高的chunk里分配
int d = maxOrder; // subpages are only be allocated from pages i.e., leaves
//
int id = allocateNode(d);
//没有节点可以分配
if (id < 0) {
return id;
}
final PoolSubpage<T>[] subpages = this.subpages;
final int pageSize = this.pageSize;
freeBytes -= pageSize;
//获取subpage的偏移索引
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();
}
int subpageIdx(int memoryMapIdx)
return memoryMapIdx ^ maxSubpageAllocs;
移除高位的值,保留地位的值,默认来说,= memoryMapIdx-maxSubpageAllocs
最后再来说说内存的释放过程,
主要的方法为void free(long handle)
void free(long handle) {
//取低位 32位获取节点
int memoryMapIdx = memoryMapIdx(handle);
//取高位32位回去subpage
int bitmapIdx = bitmapIdx(handle);
//释放subpage
if (bitmapIdx != 0) { // free a subpage
PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)];
assert subpage != null && subpage.doNotDestroy;
// Obtain the head of the PoolSubPage pool that is owned by the PoolArena and synchronize on it.
// This is need as we may add it back and so alter the linked-list structure.
PoolSubpage<T> head = arena.findSubpagePoolHead(subpage.elemSize);
synchronized (head) {
if (subpage.free(head, bitmapIdx & 0x3FFFFFFF)) {
return;
}
}
}
freeBytes += runLength(memoryMapIdx);//更新空用字节
setValue(memoryMapIdx, depth(memoryMapIdx));//还原节点的高度值
updateParentsFree(memoryMapIdx);//更新父节点
}
至此,poolchunk就差不多讲完了。