Netty_PooledByteBuf_2.PoolSubpage

一. 干什么?

概括起来如下:

  1. 当用户需要一个比较小的面积来办公时, 可以将用户安置在由一个或者多个房间的共享区域内. ---- 就像在会议室里找到一个位子.
  2. 可以满足用户不同的面积需求, 例如 有要32平的, 有要64平的, 也有要1024平的. ---- 高铁也分二等座, 一等座, 商务舱不是.
  3. 共享区域可以进行面积的分配和回收. ---- "对不起, 这个位子有人", "看, 那个位子没人".

 

二. 打算怎么干?

  1. 当用户需要的面积规格 <= smallMaxSizeIdx时, Netty将一个或者多个Page 组成一个共享区域(PoolSubpage), 用户的需求由这个共享区域来满足.

    注: smallMaxSizeIdxSizeClasses中默认为38, 38对应SizeClasses类的sizeClasses的数组下标, 对应的面积大小为28K.

  2. 用户需求的面积规格不同(16平, 48平, 1024平), 对应的共享区域的规格也会不同. 如果用户需要的面积是elemSize, 每个房间的面积是pageSize, 那么对应的共享区域的面积是: 最小公倍数(elemSize, pageSize), 这样保证共享区域可以被elemSize整除, 空间不浪费.

  3. 面积管理, 分配与回收:

    • 共享区域内的面积管理, 就像一个会议室里的座椅一样, 保持阵型, 要确定一个坐椅的位置, 知道第几排第几列就行. PoolSubpage 中由long[] bitmap来充当阵型管理.
    • 分配: 找到一个空位置, 然后标记这个空位置已占用(1-为已占用). 如果没有空位置了, 说明这个共享区域满了, 再试试其它的共享区域.
    • 回收: 根据位置的坐标定位到该位置, 标记这个位置为空置(0-空置). 如果所有的位置都是空置状态了, 那这个共享区域可以被回收了.

     

三. 具体实现:

  1. 先来看下数据结构:

    PoolSubpage初始化时: PubSubpage 初始化s

    各项注解:

    1. 图中标号: 1 chunk: 共享区域属于那一个楼层(PoolChunk).

    2. 图中标号: 3 runOffset: 共享区域(PoolSubpage)是从第几个房间(page)开始的. 示例中为0, 表明是从第0个房间开始.

    3. 图中标号: 4 runSize: 共享区域(PoolSubpage)的面积有多少, 一个共享区域可以由一个或者多个房间组成. 示例中为8192, 表示占用一个房间(默认一个房间的大小为8K).

    4. 图中标号: 5 elemSize: 每个位置的面积是多少. 示例中为32, 表示这个共享区域下, 一个位置有32平这么大, 这将是一个比较拥挤的会议室.

    5. 图中标号: 6 bitmap: 源码中根据runSize 除以最小的elemSize(16)计算出需要多少位置(maxNumElems), 再将位置分在64一组的long数组里. 注: 这里的按最小的16(elemSize)进行计算位置大小, 会有很大的空间浪费, 不明白其中的用意?.

    6. 图中标号: 7.1 elemSize: 还有为0的时候?

    7. 图中标号: 8 根据runSize与elemSize计算maxNumElems(最多需要多少位置).

    1. 图中标号: 9 bitmapLength: 因为一个long可以标记64个位置的信息, 有些大的面积需求总共(maxNumElems)下来也不到64(例如超豪华的会议室, 就放两个座位), 所以需要计算出bitmapLength进行判断限制.

    long中的每一个bit位标记位置是否分配.

    • 0 -- 占用.
    • 1 -- 空置.

    0000-0{56}-1010 : 表示:

    • 第一个位置: 无人
    • 第二个位置: 有人
    • 第三个位置: 无人
    • 第四个位置: 有人
    1. 图中标号: 10bitmap中的每一个long赋值, 为0. 会议室里所有的位置都是空着的.
    2. 图中标号: 11PoolSubpage对象进行管理, 加入到PoolArena的管理队列中, 后续再细说.
  2. 方法实现

    概括起来, 实现的方法分为两块, 一块是分配, 一块是回收.

    1. 分配:

      allocate()方法实现. 实现的过程就是从bitmap数组中, 从最初的位置(第一排第一个位置)开始找, 找到第一个还没有被人占用的位置, 然后标记此位置被征用了, 如果没有空间了, 会有一些维护操作. 最后这个返回位置的坐标信息.

      主要对应图中的 1.allocate()2.getNextAvail() 两个方法. 入口为allocate()方法,

      1. allocate()方法:

        1. 图中标号: 0 通过getNextAvail()获取可用的位置, 下面细说.

        2. 图中标号: 1

        3. 图中标号: 2

        4. 图中标号: 3

        5. 图中标号: 4

        6. 图中标号: 4.1, 如果 numAvail == 0, 说明这个共享区域中已经没有可用的位置进行分配了, 所有的位置都有人坐了, 需要对它进行管理操作, 从管理链表中移除, 我们在PoolArena中再细说.

        7. 图中标号: 5 PoolChunk 中规定由一个long中的bit位来表示从那个房间开始到那个房间结束的共享区域内, 第几排, 第几位的信息的, 详细的在PoolChunk中再细说.

         

      2. 图中标号: 0 getNextAvail() 方法是从bitmap: long[]中返回第一个空位置的坐标信息.

        1. 图中标号: 6 nextAvail 为一个缓存值, 就像刚刚有一个人离场, 那他之前的位置是空的, 当PoolSubpage刚刚初始化完成(值为0), 或者free()方法调用后, 有位置归还时, nexAvail >= 0.

        2. 图中标号: 7 ~bits != 0 这是判断bits中是否还空位置.

          如果所有的bit位都是1, 那么 ~(非运算) 的值为0, 如果~bits == 0 说明所有的bit位都是1, 没有空位置了.

          这里要注意的是, 如果long 的64个bit位不一定全都要用, 例如诺大的会议室只摆两个座位(能在这开会的级别那真是刚刚的), 那只用long最后的两位作标记位. 那么即使没有空位置了, 那~bits 也不为0, 所以下面对每一个bit位进行是否空置的判断时, 要增加 图中标号: 9.1 val < maxNumElements 的判断.

        1. 图中标号: 8 通过 (bits & 1) == 0 来判断bits的 0 bit位(最后一位) 是不是0, 如果是0表示这个位置是空置的. bits 通过for循环底部的 bits >>>= 1; 进行向右移位操作, 就是一个座位一个座位的看, 是不是有人座着:
        1. 图中标号: 9 int val = i << 6 | j;

       

    2. 回收:

      通过传入参数bitmapIdx(座的位置信息), 由free()方法来更新对应位置的标记为闲置状态. 更新完成后, 如果bitmap数组中的所有标记位都是闲置状态, 这个会议室是空的, 那么该共享区域可以整体被回收.

      1. 图中标号: 10 通过异或运算, 设置bitmap[q]: long 中的第r个位置(0为初始位置)为0:

        异或运算

      2. 图中标号: 11, 设置nextVal 一个类似缓存的值.

      3. 图中标号: 12, numAvail ++ == 0, 这里是先进行 numAvail == 0的判断, 然后再对numAvail ++. 如果numAvail 初始值为0, 表示没有座位是空的, 现在进行free操作, 那可分配的座位数就大于0了, 则这个PoolSubpage共享区域是可以满足下一个分配需求的(之前会议室里满员了, 现在一个人有事要回家, 那肯定就有空位置了), 需要把这个PoolSubpage放在管理队列里, addToPool()就是完成这个操作, 后续我们再细说.

      4. 图中标号: 13.

      5. 图中标号: 14. 类似于管理缓存的操作, 后面细说.

      6. 图中标号: 15. 对PoolSubpage整个共享空间进行回收. 后续细说.

    3. 分配与回收示例:
      1. 图中标号: 16. PoolSubpage的参数为: 8192: runSize, 32: eleSize, 256: maxNumElems, 4: bitmapLength, pre ! = next.

      2. 0.init(): 初始化时的数据结构.

        • 图中标号: 17 bitmap 数组所有bit位都是0. 新开的会议室肯定是没人的.
        • 图中标号: 18 nextAvail 这时候等于0, 第一个位置是空的.
      3. 1.allocate(): 第一次分配后的数据变化.

        • 图中标号: 19 第一次allocate(), [0]下标的long 的第0 bit位更新成1, 表明这个位置有人坐了.
        • 图中标号: 20 nextAvail = -1, 缓存的有用位置变更为无效.
        • 图中标号: 21 返回的bitmapIdx为0. 0排, 0位.
      4. 2.allocate(): 第二次分配后的数据变化.

        • 图中标号: 22 第一次allocate(), [0]下标的long 的第1 bit位更新成1.
        • 图中标号: 23 返回的bitmapIdx为1. 0排, 1位.
      5. 11.free(, 0: bitmapIdx): free 0:bitmapIdx后的数据变化.

        • 图中标号: 25 根据bitmapIdx(0) 设置第0排, 第0位为空置.
        • 图中标号: 26 nextAvail = 0, 缓存变更.
        • 图中标号: 27 这里返回true.
      6. 12.free(, 1: bitmapIdx): free 1:bitmapIdx后的数据变化.

        • 图中标号: 29 根据bitmapIdx(1) 设置第0排, 第1位为空置.
        • 图中标号: 30 nextAvail = 1, 缓存变更.
        • 图中标号: 31 这里返回false, 这里的false表示位置回收, 共享区域也要回收.
        • 图中标号: 32 doNotDestroy = false, 共享区域也回收了.

     

四. 总结:

"阿杜看源码"所有稿件, 未经正式授权一律不得转载、出版、改编,或进行与"阿杜看源码"版权相关的其他行为,违者必究!