前面讲过PoolChunk最小得分配单位是page,而page得默认大小是8k,但在实际应用当中,会有很多的小数据,如果小数据也占用一个page的话。那无疑内存将会大大的浪费。针对这种情况,netty就建了个新东西叫PoolSubpage。PoolSubpage的工作和PoolChunk类似,只是PoolSubpage所管理的内存块远小于PoolChunk,且PoolSubpage的内存是从chunk的最深那一层分割的。
首先,看下PoolSubpage的字段
final PoolChunk<T> chunk;//所属的poolChunk
private final int memoryMapIdx;//poolchunk的id
private final int runOffset;//当前page在chunk里的偏移量
private final int pageSize;//page大小
private final long[] bitmap;//各个poolsubpage的使用状态
PoolSubpage<T> prev;//前节点
PoolSubpage<T> next;
boolean doNotDestroy;//是否能清除
int elemSize;//把page分割的大小
private int maxNumElems;//总共有多少个elem
private int bitmapLength;//bitmap需要用到的长度
private int nextAvail;//下一个可用节点位置
private int numAvail;//可用节点的数量
PollSubPage的字段就只有这些,需要特别注意的就是bitmap,是个long类型的数组,long是64位,每一位都代表多应的节点是否可用。
构造方法
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);
}
构造方法很简单,要注意的就是bitmap的长度赋值了。为什么会是 pageSize >>>10呢?那是因为用户请求分配的大小在内存分配这条链上已经被normalizeCapacity处理了,这个方法在PoolArena中,最小值是16,而long类型的长度是16,代表一个long类型的数据能表示64个节点是否已经被使用,所以要完整的表示所有的节点数是否被使用 所需要的 long个数= pageSzie /16 / 64 =pageSize/(2^4*2^6)=pageSzie /2^10 =pageSize >>>10。
void init(PoolSubpage<T> head, int elemSize)
//从IDE里可以看到,这个方法会在两个地方调用,1.初始化;2.subPage被回收后在分配
void init(PoolSubpage<T> head, int elemSize) {
doNotDestroy = true;
this.elemSize = elemSize;
//
if (elemSize != 0) {//初始化
maxNumElems = numAvail = pageSize / elemSize;//计算节点的最大值
nextAvail = 0;//把下一节点置为可用
bitmapLength = maxNumElems >>> 6;//得到bitmap的长度 相当于 / 64
if ((maxNumElems & 63) != 0) {当个数小于64得话,maxNumElems >>> 6=0,所以需要补偿
bitmapLength ++;
}
for (int i = 0; i < bitmapLength; i ++) {//把所有节点置为没有使用状态
bitmap[i] = 0;
}
}
//加入到池里,之所以要加入到池里是因为chunk分配了subPage,但并没有把subPage暴露出去,所以需要自己把自己加入到chunk.arena里,以便管理
addToPool(head);
}
构造方法和字段说完,下面来看看PoolSubPage的核心方法,
long allocate()
long allocate() {
if (elemSize == 0) {
return toHandle(0);
}
//没有可用节点或已经被销毁了
if (numAvail == 0 || !doNotDestroy) {
return -1;
}
//获取到可用节点
final int bitmapIdx = getNextAvail();
//算出可用节点在bitmap里的索引
int q = bitmapIdx >>> 6;
//计算出long值得第几位表示这个索引
int r = bitmapIdx & 63;
assert (bitmap[q] >>> r & 1) == 0;//没被使用校验
bitmap[q] |= 1L << r;//把long对应得位数置为1
if (-- numAvail == 0) {//把可用节点数减一,等于0表示没有可用内存,就把subpage从arena里删除
removeFromPool();
}
//返回获取到得内存索引,是一个long类型,其中高32位代表chunk得id,低32位代表subpage得id
return toHandle(bitmapIdx);
}
接下来看下是如何寻找节点得
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 ++) {//对bitmap遍历
long bits = bitmap[i];
if (~bits != 0) {//取非,不等于0说明该long值上有位数是=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) {//等于0说明该位置还没有被分配
int val = baseVal | j;//得出在所有的节点里,这是第几个
if (val < maxNumElems) {//小于最大节点数量,就返回这个节点
return val;
} else {
break;
}
}
bits >>>= 1;
}
return -1;
}
到这里,PoolSubPage的分配内存,初始化就讲完了,除了一些位运算有点饶人外,其他的还是比较简单的。
最后看看PoolSubPage是怎么释放内存的
boolean free(PoolSubpage<T> head, int bitmapIdx)
boolean free(PoolSubpage<T> head, int bitmapIdx) {
if (elemSize == 0) {
return true;
}
//获取到对应节点
int q = bitmapIdx >>> 6;
int r = bitmapIdx & 63;
assert (bitmap[q] >>> r & 1) != 0;
bitmap[q] ^= 1L << r;
//设置位可用
setNextAvail(bitmapIdx);
//numAvail==0说明之前已经把subpage从arena里删除了,这里重新加入
if (numAvail ++ == 0) {
addToPool(head);
return true;
}
if (numAvail != maxNumElems) {
return true;
} else {//下面一整段是做了下性能处理,
if (prev == next) {
return true;
}
doNotDestroy = false;
removeFromPool();
return false;
}
}
-----------------------------------------------------------------------
百世经纶,扑街之路,不由分说