Netty版本:4.1.38
作用&使用场景
- 一个pageSize默认是8k,仅对一些场景分配8k,太过浪费;
- PoolSubpage来进行精细化分配小内存场景
- PoolSubpage是为了更好的管理subpage而服务的
- 一个PoolSubpage 实际上对应一个pageSize(8k)
思维导图
整体流程图
初始化&申请流程图
源码解析
init
这个的作用是为了初始化PoolArena中的tinySubpagePools数组、smallSubpagePools数组的第一个元素,
PoolSubpage(int pageSize) {
chunk = null;
memoryMapIdx = -1;
runOffset = -1;
elemSize = -1;
this.pageSize = pageSize;
bitmap = null;
}
这个是申请内存、真正调用的初始化方法
PoolSubpage(PoolSubpage<T> head, PoolChunk<T> chunk, int memoryMapIdx, int runOffset, int pageSize, int elemSize) {
this.chunk = chunk; // 哪块chunk
this.memoryMapIdx = memoryMapIdx; // 对应二叉搜索树里面的索引
this.runOffset = runOffset; // 代表PoolSubpage对象的内存地址
this.pageSize = pageSize; // 默认8k
// bitmap中每一个bit位都代表一个subpage的elemSize大小;
bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
init(head, elemSize);
}
void init(PoolSubpage<T> head, int elemSize) {
doNotDestroy = true;
this.elemSize = elemSize;
if (elemSize != 0) {
// 获取当前可用的(最大的)元素个数
maxNumElems = numAvail = pageSize / elemSize;
nextAvail = 0;
// 获取当前所需要的bitmap的数组长度; 所以这里就看到了 bitmapLength != bitmap.length
// 这里就有冗余了,高版本(比如:4.1.112)做了优化;
// 这里就代表只需要多少个long类型的数字就可以表示
bitmapLength = maxNumElems >>> 6;
if ((maxNumElems & 63) != 0) {
bitmapLength ++;
}
for (int i = 0; i < bitmapLength; i ++) {
bitmap[i] = 0;
}
}
// 将当前的PoolSubpage对象添加到tinySubpagePools 或者smallSubpagePools的链表中;
addToPool(head);
}
allocate
long allocate() {
if (elemSize == 0) {
return toHandle(0);
}
if (numAvail == 0 || !doNotDestroy) {
return -1;
}
// 获取当前bitmapIdx指针,有两方面组成,大于64,表示bitmap数组的索引位,小于64的数字表示bitmap[i]中的第几位bit;
// 比如:elemSize = 32k, bitmapIdx = 67 ==> bitmap[1] = 4;也就是说索引位是1的bitmap数字的第4位bit对应的subpage已经被占用了;
final int bitmapIdx = getNextAvail();
int q = bitmapIdx >>> 6;
int r = bitmapIdx & 63;
assert (bitmap[q] >>> r & 1) == 0;
// 分配完成,将该bit位标记为1
bitmap[q] |= 1L << r;
// 如果PoolSubpage被占用完了,就将其移除
if (-- numAvail == 0) {
removeFromPool();
}
return toHandle(bitmapIdx);
}
private int getNextAvail() {
int nextAvail = this.nextAvail;
// nextAvail =0 ,表示首次分配;
// nextAvail>0 ,表示有一些subpage被free,这里在重用;所以直接取就行;
if (nextAvail >= 0) {
this.nextAvail = -1;
return nextAvail;
}
// 这里就是最常用获取subpage的过程
return findNextAvail();
}
private int findNextAvail() {
final long[] bitmap = this.bitmap;
final int bitmapLength = this.bitmapLength;
// 1、 先遍历bitmap数组
for (int i = 0; i < bitmapLength; i ++) {
long bits = bitmap[i];
if (~bits != 0) {
// 只有bitmap[i] 中还有空余bit位不等于1;
return findNextAvail0(i, bits);
}
}
return -1;
}
private int findNextAvail0(int i, long bits) {
final int maxNumElems = this.maxNumElems;
final int baseVal = i << 6;
// 遍历long类型的位数(long有64个字节)
for (int j = 0; j < 64; j ++) {
// 找到第一个bit位=0
if ((bits & 1) == 0) {
// 相或,然后返回
int val = baseVal | j;
if (val < maxNumElems) {
return val;
} else {
break;
}
}
// 如果对应的bits为=1, 就将bit右移1位;
bits >>>= 1;
}
return -1;
}
内存释放的流程图
源码解析
boolean free(PoolSubpage<T> head, int bitmapIdx) {
if (elemSize == 0) {
return true;
}
// 从bitmapIdx 还原索引位,bit位;
int q = bitmapIdx >>> 6;
int r = bitmapIdx & 63;
assert (bitmap[q] >>> r & 1) != 0;
bitmap[q] ^= 1L << r;
// 这里 和getNextAvail方法里面的nextAvail相呼应了;
setNextAvail(bitmapIdx);
// 这里和allocate中的numAvail==0--> removeFromPool相呼应
if (numAvail ++ == 0) {
addToPool(head);
return true;
}
if (numAvail != maxNumElems) {
return true;
} else {
// Subpage not in use (numAvail == maxNumElems)
if (prev == next) {
// Do not remove if this subpage is the only one left in the pool.
return true;
}
// Remove this subpage from the pool if there are other subpages left in the pool.
doNotDestroy = false;
// 如果PoolSubpage中的subpage都被回收了,那么就可以考虑将其回收到PoolChunk,提供给其他人用
removeFromPool();
return false;
}
}
高低版本针对bitmap的优化
4.1.38版本中的PoolSubpage
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;
// 根据pageSize(默认8k)简单粗暴,右移10位,
// 为何右移10位,这是由于低版本的最低内存单位是16B, 每个long类型是64位
// 这么做是有冗余申请的;因为bitmap中的每一位都代表着elemSize大小;
// 所以在高版本就给优化了
bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
init(head, elemSize);
}
4.1.112版本的Netty中的PoolSubpage
PoolSubpage(PoolSubpage<T> head, PoolChunk<T> chunk, int pageShifts, int runOffset, int runSize, int elemSize) {
......
// 获取当前能够保存多少个elemSize的元素
maxNumElems = numAvail = runSize / elemSize;
// 获取最大可保存元素后,在除以64,表示bitmap中的每一位都代表elemSize大小
int bitmapLength = maxNumElems >>> 6;
// 如果有余数,则bitmapLength ++;
if ((maxNumElems & 63) != 0) {
bitmapLength ++;
}
this.bitmapLength = bitmapLength;
bitmap = new long[bitmapLength];
nextAvail = 0;
lock = null;
addToPool(head);
}
学习&总结
1、 位运算
- 速度快,就需要极致的考究,最贴近计算机语言,才能达到极致的速度;
- handle指针:一个long类型数据,根据高低位不同,代表不同的含义;一方面可以节省内存,另一方面又可以保障操作的原子性;这一点和ForkJoinPool中的ctl等变量有异曲同工之妙
2、大而化小,动态回收、分配内存
大块操作虽然简单,但是浪费空间; 所以针对小内存的场景,可以根据subpage来动态分配;
- 如果PoolSubpage已经全部分配完,那可以从smallSubpagePools或者tinySubpagePools中移除了;简化操作;
- 如果PoolSubpage中的某些subpage被free,同样再给添加回来;在进行管理;如果PoolSubpage中的subpage都回收了,那可以将这个Subpage对应的page页归还给PoolChunk;
参看网址