概括起来如下:
当用户需要的面积规格 <= smallMaxSizeIdx时, Netty将一个或者多个Page 组成一个共享区域(PoolSubpage), 用户的需求由这个共享区域来满足.
注: smallMaxSizeIdx 在SizeClasses中默认为38, 38对应SizeClasses类的sizeClasses的数组下标, 对应的面积大小为28K.
用户需求的面积规格不同(16平, 48平, 1024平), 对应的共享区域的规格也会不同. 如果用户需要的面积是elemSize, 每个房间的面积是pageSize, 那么对应的共享区域的面积是: 最小公倍数(elemSize, pageSize), 这样保证共享区域可以被elemSize整除, 空间不浪费.
面积管理, 分配与回收:
PoolSubpage初始化时:
各项注解:
图中标号: 1 chunk: 共享区域属于那一个楼层(PoolChunk).
图中标号: 3 runOffset: 共享区域(PoolSubpage)是从第几个房间(page)开始的. 示例中为0, 表明是从第0个房间开始.
图中标号: 4 runSize: 共享区域(PoolSubpage)的面积有多少, 一个共享区域可以由一个或者多个房间组成. 示例中为8192, 表示占用一个房间(默认一个房间的大小为8K).
图中标号: 5 elemSize: 每个位置的面积是多少. 示例中为32, 表示这个共享区域下, 一个位置有32平这么大, 这将是一个比较拥挤的会议室.
图中标号: 6 bitmap: 源码中根据runSize 除以最小的elemSize(16)计算出需要多少位置(maxNumElems), 再将位置分在64一组的long数组里. 注: 这里的按最小的16(elemSize)进行计算位置大小, 会有很大的空间浪费, 不明白其中的用意?.
图中标号: 7.1 elemSize: 还有为0的时候?
图中标号: 8 根据runSize与elemSize计算maxNumElems(最多需要多少位置).
- 图中标号: 9 bitmapLength: 因为一个long可以标记64个位置的信息, 有些大的面积需求总共(maxNumElems)下来也不到64(例如超豪华的会议室, 就放两个座位), 所以需要计算出bitmapLength进行判断限制.
long中的每一个bit位标记位置是否分配.
- 0 -- 占用.
- 1 -- 空置.
0000-0{56}-1010 : 表示:
- 第一个位置: 无人
- 第二个位置: 有人
- 第三个位置: 无人
- 第四个位置: 有人
- 图中标号: 10 对bitmap中的每一个long赋值, 为0. 会议室里所有的位置都是空着的.
- 图中标号: 11 对PoolSubpage对象进行管理, 加入到PoolArena的管理队列中, 后续再细说.
概括起来, 实现的方法分为两块, 一块是分配, 一块是回收.
分配:
由allocate()方法实现. 实现的过程就是从bitmap数组中, 从最初的位置(第一排第一个位置)开始找, 找到第一个还没有被人占用的位置, 然后标记此位置被征用了, 如果没有空间了, 会有一些维护操作. 最后这个返回位置的坐标信息.
主要对应图中的 1.allocate()与 2.getNextAvail() 两个方法. 入口为allocate()方法,
allocate()方法:
图中标号: 0 通过getNextAvail()获取可用的位置, 下面细说.
图中标号: 1
xxxxxxxxxx
int q = bitmapIdx >>> 6; // 获取到bitmap[]数组的下标, 可以认为是会议室的第几排, 可以看 图中标号:9 , bitmapIdx的生成.
图中标号: 2
xxxxxxxxxx
int r = bitmapIdx & 63; // 在long 的64位中, 是属于第几个bit位, 可以看 图中标号:9 , bitmapIdx的生成.
// 这里数字63的二进制表示为: 0000-0{52}-0011-1111
// bitmapIdx & 63 就只是取bitmapIdx 后6位的数据, 转换成一个整数, 表示在会议室第N排中的第几个位置.
图中标号: 3
xxxxxxxxxx
bitmap[q] |= 1L << r ; // 将bitmap中对应的标记位设置为1, 表示这个位置现在被征用了.
图中标号: 4
xxxxxxxxxx
if( -- numAvail == 0) // 根据运算符的优先级, 这里先对 numAvail 进行 -- 操作, 再对 (-- numAvail) == 0 进行判断
图中标号: 4.1, 如果 numAvail == 0, 说明这个共享区域中已经没有可用的位置进行分配了, 所有的位置都有人坐了, 需要对它进行管理操作, 从管理链表中移除, 我们在PoolArena中再细说.
图中标号: 5 PoolChunk 中规定由一个long中的bit位来表示从那个房间开始到那个房间结束的共享区域内, 第几排, 第几位的信息的, 详细的在PoolChunk中再细说.
xxxxxxxxxx
return 0:runOffset << 49:RUN_OFFSET_SHIFT | (long) pages << 34:SIZE_SHIFT
| 1L << 33:IS_USED_SHIFT | 1L << 32:IS_SUBPAGE_SHIFT | bitmapIdx;
图中标号: 0 getNextAvail() 方法是从bitmap: long[]中返回第一个空位置的坐标信息.
图中标号: 6 nextAvail 为一个缓存值, 就像刚刚有一个人离场, 那他之前的位置是空的, 当PoolSubpage刚刚初始化完成(值为0), 或者free()方法调用后, 有位置归还时, nexAvail >= 0.
图中标号: 7 ~bits != 0 这是判断bits中是否还空位置.
如果所有的bit位都是1, 那么 ~(非运算) 的值为0, 如果~bits == 0 说明所有的bit位都是1, 没有空位置了.
这里要注意的是, 如果long 的64个bit位不一定全都要用, 例如诺大的会议室只摆两个座位(能在这开会的级别那真是刚刚的), 那只用long最后的两位作标记位. 那么即使没有空位置了, 那~bits 也不为0, 所以下面对每一个bit位进行是否空置的判断时, 要增加 图中标号: 9.1 val < maxNumElements 的判断.
xxxxxxxxxx
~1111; // = 0000
~0000; // = 1111
~0011; // = 1100 这里只是最后两位用来标记.
- 图中标号: 8 通过 (bits & 1) == 0 来判断bits的 0 bit位(最后一位) 是不是0, 如果是0表示这个位置是空置的. bits 通过for循环底部的 bits >>>= 1; 进行向右移位操作, 就是一个座位一个座位的看, 是不是有人座着:
xxxxxxxxxx
0000-1111 >>> 1; // = 0000-0111
0000-0111 >>> 1; // = 0000-0011
0000-0111 & 1 ; // = 0000-0001
0000-0110 & 1 ; // = 0000-0000
- 图中标号: 9 int val = i << 6 | j;
xxxxxxxxxx
int val = i << 6 | j; // 其中 i 为bitmap[]数据的下标, 表示第几个long, 会议室中的第几排. j 为在long中的第几位, 位置是第几个.
val = 1 << 6 | 4; // = 0100-0100 // 1 为bitmap[]中的第二个long, 4 表示在long中的第4个bit位.
回收:
通过传入参数bitmapIdx(座的位置信息), 由free()方法来更新对应位置的标记为闲置状态. 更新完成后, 如果bitmap数组中的所有标记位都是闲置状态, 这个会议室是空的, 那么该共享区域可以整体被回收.
图中标号: 10 通过异或运算, 设置bitmap[q]: long 中的第r个位置(0为初始位置)为0:
xxxxxxxxxx
bitmap[q] ^= 1L << r ;
// q, r 从bitmapIdx中获取, 就像会议室中的第几排, 第几个座位. 参考 图中标号: 9 bitmapIdx的生成.
// ^ 为异或运算, 异或运算可以参看下图. 与图中标号: 10.1, 10.2
// 例如: q = 1, r = 4;
1L << 4 ; // = 0001-0000
bitmap[1]; // 原值为 0001-1000
bitmap[1] ^= 1L << 4 ; // bitmap[1] = 0000-1000
图中标号: 11, 设置nextVal 一个类似缓存的值.
图中标号: 12, numAvail ++ == 0, 这里是先进行 numAvail == 0的判断, 然后再对numAvail ++. 如果numAvail 初始值为0, 表示没有座位是空的, 现在进行free操作, 那可分配的座位数就大于0了, 则这个PoolSubpage共享区域是可以满足下一个分配需求的(之前会议室里满员了, 现在一个人有事要回家, 那肯定就有空位置了), 需要把这个PoolSubpage放在管理队列里, addToPool()就是完成这个操作, 后续我们再细说.
图中标号: 13.
xxxxxxxxxx
if(numAvail != maxNumElems) // 空置的座位数 != 会议的可容纳的人数, 说明free()操作后还有位置是被占用的.
if(numAvail == maxNumElems) // 说明free()操作后, 所有的位置都释放了, 那整个PoolSubpage也可以被回收.
图中标号: 14. 类似于管理缓存的操作, 后面细说.
图中标号: 15. 对PoolSubpage整个共享空间进行回收. 后续细说.
分配与回收示例:
图中标号: 16. PoolSubpage的参数为: 8192: runSize, 32: eleSize, 256: maxNumElems, 4: bitmapLength, pre ! = next.
0.init(): 初始化时的数据结构.
- 图中标号: 17 bitmap 数组所有bit位都是0. 新开的会议室肯定是没人的.
- 图中标号: 18 nextAvail 这时候等于0, 第一个位置是空的.
1.allocate(): 第一次分配后的数据变化.
- 图中标号: 19 第一次allocate(), [0]下标的long 的第0 bit位更新成1, 表明这个位置有人坐了.
- 图中标号: 20 nextAvail = -1, 缓存的有用位置变更为无效.
- 图中标号: 21 返回的bitmapIdx为0. 0排, 0位.
2.allocate(): 第二次分配后的数据变化.
- 图中标号: 22 第一次allocate(), [0]下标的long 的第1 bit位更新成1.
- 图中标号: 23 返回的bitmapIdx为1. 0排, 1位.
11.free(, 0: bitmapIdx): free 0:bitmapIdx后的数据变化.
- 图中标号: 25 根据bitmapIdx(0) 设置第0排, 第0位为空置.
- 图中标号: 26 nextAvail = 0, 缓存变更.
- 图中标号: 27 这里返回true.
12.free(, 1: bitmapIdx): free 1:bitmapIdx后的数据变化.
- 图中标号: 29 根据bitmapIdx(1) 设置第0排, 第1位为空置.
- 图中标号: 30 nextAvail = 1, 缓存变更.
- 图中标号: 31 这里返回false, 这里的false表示位置回收, 共享区域也要回收.
- 图中标号: 32 doNotDestroy = false, 共享区域也回收了.