0
点赞
收藏
分享

微信扫一扫

内存管理--PoolChunk&PoolSubPage

爱情锦囊 2023-05-02 阅读 213
javaNetty

一、写在前面

想必你已知道内存池实现需要兼顾分配效率和空间利用率, 通过对实际业务的观察你会发现, 通常小内存的申请和回收是比较频繁的, 大内存使用相对较少。比如创建16个元素以内的链表或者顺序表比较常见, 有160个元素的比较少。因此, 针对大小内存申请分开考虑是个不错的注意。在Netty 4.1.73中就把尺寸分为2个大类, Normal和Small。其中small级别的分配是在normal的基础上进行。相当于分配都是按page分配, 但是针对小于page颗粒度的内存又多了一些独立管理。

二、基本分配流程

  1. PoolChunk先分配若干page;
  2. PoolSubPage将若干page合并为一个整体做更细粒度的管理;

三、PoolChunk分配page流程

1. 维护字段

字段作用
requestSize申请空间大小
runSize分配空间大小
runAvail队列数组, 数组的索引为尺寸索引(相当于一个size的bucket), 队列内容为某段空闲区域内startOffset, 相对于整个chunk
runAvailMap所有空闲区域的startOffset和endOffset

2. 具体过程

  1. 尺寸规格化
    转换requestSize为runSize;
  2. 找到目标尺寸队列
    runAvail中从runSize的index开始搜索非空的Queue, 如果当前index对应的queue为empty则增大index(index越大其对应的size也越大,具体参见PoolChunk#runFirstBestFit);
  3. 获得可分配尺寸的offset
    从queue中poll一个元素, 并获得其handle(其中包含page数量和startoffset),基于page数量和startOffset计算endOffset(也就是可用空间的startOffset和endOffset)
  4. 分配出目标空间
    从startOffSet和endOffSet中分配runSize对应的空间产生newStartOffset, 划分的空间作为最终结果返回;
  5. 剩余空间放回:
    a. 维护runAvailMap, 将newStartOffset和endOffSet重新放入runAvailMap中;
    b. 维护runAvail, 根据其中pages数量得到runAvail中的索引, 将startOffset加入到队列中;
  6. 一次分配结束;

四、PoolSubPage分配subPage流程

1. PoolSubPage的由来

首先, 想必你已知道PoolSubPage则是将几个Page合并统一管理, 其中的元素尺寸是小于pageSize(默认8KB)的。从这个角度来说, PoolSubPage和PoolChunk的区别在分配空间的最小尺寸(最小单位上), 而不是SubPage的整个空间容量低于pageSize。(这个点个人过去误解过)

其次, 如何根据requestCapacity计算PoolSubPage的空间大小? Netty会把请求的尺寸规格化之后作为一次内存分配的空间大小, 然后计算一个pageSize和requestCapacity的合适公倍数作为runSize(也就是实际分配的空间大小)。例如, 请求100B, 规格化之后为112B, 实际分配的空间56K, 元素大小112, 元素数量512。因此, 分配空间时, 其中的元素大小和能够分配的元素数量也就确定了。

最后, 整个PoolSubPage基于上述的空间占用标记和总的元素个数做内存空间的管理。

2. 维护字段

字段作用
bitMap空间占用标记, 每个位标识一块内存, 位所在的索引即为空间块索引
bitMapLengthbitMap长度
maxElement最大元素数量,通常为512

3. 分配过程

  1. 结合bitMap和bitMapLength找到一个可用空间, 同时更新bitMap中目标index的位;
  2. 如果bitMap中已没有可用空间, 说明全部分配, 则从PoolSubPage中删除;

4. 回收过程

  1. 空间释放则更新bitMap目标index位为0;
  2. 如果整个SubPage空间全部未使用, 则判断当前SubPage是否为Pool中的唯一; 如果非唯一则释放掉, 否则保留;

五、多颗粒度

  1. PoolSubPage是其中的SubPage的容器, 如果SubPage空间释放, 则应归还到PoolSubPage;
  2. PoolArena是其中的PoolSubPage的容器, 如果PoolSubPage空间释放, 则应归还到PoolArena;
  3. Netty中考虑到小内存的复用率比较高, 因此PoolArena中对于同一尺寸的PoolSubPage会尝试至少保留一个。

六、小结

以上就是个人基于Netty4.1.73.Final源码了解到的PoolChunk和PoolSubPage的内存分配与回收过程。相比于旧版本, 一方面管理的颗粒度合并为两级Normal和Small。另外PoolChunk层面已经看不到伙伴分配算法的身影了, 取而代之的就是多少个page和对应的runOffset。PoolSubPage层面和之前类似, 仍然基于bitMapIndx、elementSize和maxElements来做具体的分配和管理。

举报

相关推荐

0 条评论