PoolChunk用来分配大于或等于一个page的内存,如果需要小于一个page的内存,需要先从PoolChunk中分配一个page,然后再把一个page切割成多个子页-subpage,最后把内存以subpage为单位分配出去。PoolSubpage就是用来管理subpage的。

一个page会被分割成若干个大小相同的subpage,subpage的的大小是elemSize。elemSize必须是16的整数倍,即必须满足elemSize & 15 == 0。elemSize的取值范围是(16, pageSize-16)。多个elemSize相等的PoolSubpage组成一个双向链表,用于分配特定大小的subpage。

Tiny和Small类型的内存,都是用subpage来分配。Tiny内存大小范围是[16, 512),如果把大小不同的subpage按顺序排列,除最后一个外,任意一个subpage的elemSize+16等于下一个subpage的elemSize,可以用于分配Tiny内存的subpage有512>>4=32种。Small内存大小范围是[512, pageSize),subpage的elemSize=512 * 2n = 2(9+n), 可以用于分配Small内存的subpage有n种,n的最小值是0, 最大值由pageSize决定。

已知:

elemSize < pageSize

pageSize可以表示为2k

elemSize = 29+n

=> 29+n < 2k

=> 9+n < k

=> n < k - 9

=> n的取值范围是(0, k - 9)

上一章中分析过pageShifts, 它就是上面推导过程中使用的变量k。

PoolArena中的PoolSubpage数组

PoolArena维护了两个PoolSubpage表,都是以PoolSubpage[]数组的形式保存:

tinySubpagePools:用于分配Tiny内存,数组长度是521 >> 4 = 32。

smallSubpagePools: 用于分配Small内存,数组长度是pageShifts - 9。

PoolArean在构造方法中初始化这两个数组:

1 tinySubpagePools =newSubpagePoolArray(numTinySubpagePools);2 for (int i = 0; i < tinySubpagePools.length; i ++) {3 tinySubpagePools[i] =newSubpagePoolHead(pageSize);4 }5

6 numSmallSubpagePools = pageShifts - 9;7 smallSubpagePools =newSubpagePoolArray(numSmallSubpagePools);8 for (int i = 0; i < smallSubpagePools.length; i ++) {9 smallSubpagePools[i] =newSubpagePoolHead(pageSize);10 }

代码中的numTinySubpagePools=512>>4和numSmallSubpagePools=pageShifts - 9,分别是两个数组的长度。这两个数组保存的都是PoolSubpage双向链表的头节点,头节点不能用来分配内存。

findSubpagePoolHead方法可以根据elemSize找到对应的PoolSubpage链表的头节点:

1 PoolSubpage findSubpagePoolHead(intelemSize) {2 inttableIdx;3 PoolSubpage[] table;4 if (isTiny(elemSize)) { //< 512

5 tableIdx = elemSize >>> 4;6 table =tinySubpagePools;7 } else{8 tableIdx = 0;9 elemSize >>>= 10;10 while (elemSize != 0) {11 elemSize >>>= 1;12 tableIdx ++;13 }14 table =smallSubpagePools;15 }16

17 returntable[tableIdx];18 }

4-6行,如果是Tiny内存,计算elemSize在tinySubpagePools中的偏移量tableIdx。

8-14行,如果是Normal内存,计算elemSize在smallSubpagePools中的偏移量tabIeIdx。计算tableIdx的算法是把elemSize无符号右移10位之后,找非0的最高位,在找的过程中累加tableIdx,找到之后及得到了正确的偏移量。这个算法还可以简化成log2(elemSize) - 9。

17行,取出一个PoolSubpage链表头。

PoolSubpage初始化

在PoolChunk的allocateSubpage方法中,调用findSubpagePoolHead得到一个head,然后使用分配到的二叉树内存节点初始化一个PoolSubpage节点。一个能用来分配内存的PooSubpage节点可以调用构造方法或init方法进行初始化。

1 PoolSubpage(PoolSubpage head, PoolChunk chunk, int memoryMapIdx, int runOffset, int pageSize, intelemSize) {2 this.chunk =chunk;3 this.memoryMapIdx =memoryMapIdx;4 this.runOffset =runOffset;5 this.pageSize =pageSize;6 bitmap = new long[pageSize >>> 10]; //pageSize / 16 / 64

7 init(head, elemSize);8 }

这个构造方法只是做了一些简单的属性初始化工作。第6行初始bitmap,它用bit位来记录每个subpage的使用情况,每个bit对应一个subpage,0表示subpage空闲,1表示subpage已经被分配出去。一个subpage的大小是elemSize,前面已经讲过,最小的elemSize=16,  那么一个page最多可分割成subpage的数量maxSubpageCount=pageSize/16=pageSize >> 4。bitmap是个long型的数字,每个long数据有64位,因此bitmap的最大长度只需要maxBitmapLength = maxSubpageCount / 64 = pageSize / 16 / 64 = pageSize >> 10就够用了。

init方法的作用是根据elemSize计算出有效的bitmap长度bitmapLength,然后把bitmapLength范围内存的bit值都初始化为0。

1 void init(PoolSubpage head, intelemSize) {2 doNotDestroy = true;3 this.elemSize =elemSize;4 if (elemSize != 0) {5 maxNumElems = numAvail = pageSize /elemSize;6 nextAvail = 0;7 bitmapLength = maxNumElems >>> 6;8 if ((maxNumElems & 63) != 0) {9 bitmapLength ++;10 }11

12 for (int i = 0; i < bitmapLength; i ++) {13 bitmap[i] = 0;14 }15 }16 addToPool(head);17 }

第5行,初始subpage的最大数量maxNumElems和可用数量numAvail。

第6行,初始化下一个可用subpage的索引。

第7行, 计算bitmap的有效数量,bitmapLength = maxNumElems >>> 6 = maxNumElems / 64。

第8,9行,如果maxNumElems不是64的整数倍,bitmapLength需要额外加1。

第12-14行,有效长度的bitmap值都设置成0。

第16行, 把当前PoolSubpage节点添加到head后面。

bitmap位的索引范围是[0, maxNumElems)。

分配一个subpage

PoolSubpage初始化完成之后,调用allocate可以分配一个subpage,返回的是一个long型的的handle,这个handle代表一块内存。handle的低32位memoryMapIdx,是PoolChunk中二叉树节点的索引;高32位bitmapIdx,是subpage在bitmap中对应位的索引。

分配一个subpage有两个步骤:

找到一个可用subpage的索引bitmapIdx

把这个bitmapIdx在bitmap中对应的bit为置为1

findNextAvail方法负责到底一个可用的subpage并返回它的bitmapIdx。

1 private intfindNextAvail() {2 final long[] bitmap = this.bitmap;3 final int bitmapLength = this.bitmapLength;4 for (int i = 0; i < bitmapLength; i ++) {5 long bits =bitmap[i];6 if (~bits != 0) {7 returnfindNextAvail0(i, bits);8 }9 }10 return -1;11 }12

13 private int findNextAvail0(int i, longbits) {14 final int maxNumElems = this.maxNumElems;15 final int baseVal = i << 6;16

17 for (int j = 0; j < 64; j ++) {18 if ((bits & 1) == 0) {19 int val = baseVal |j;20 if (val >>= 1;27 }28 return -1;29 }

第1-11行,变量bitmap数组,找到一个至少有一位是0的long数据。~bits != 0 说明bits中至少有一位是0。然后调用findNextAvail0找到bits中为0的最低位。

第15行,计算bitmap数组中的索引i对应的bit索引baseVal = i << 6 = i * 64。

第17-27行,遍历bits的每个bit位,遇到为0的bit后在19计算回bitmpaIdx = baseVal | j,j表示bit位在long数据中bit索引。如果满足bitmapIdx < maxNumElems在21返回。

第28行,如果没找到可用的subpage, 返回-1。当maxNumElems不是64的整数倍时,bitmap数组中最后一个bits在~bits != 0的情况下可能已经没有subpage可用。

allcate方法是分配supage的入口,它调用getNextAvail得到一个supage的bitmapIdx,  getNextAvail在nextAvail属性为-1的时候,调用findNexAvail。然后把bitmapIdx对应的bit为置为1,最后返回handle。

1 longallocate() {2 if (elemSize == 0) {3 return toHandle(0);4 }5

6 if (numAvail == 0 || !doNotDestroy) {7 return -1;8 }9

10 final int bitmapIdx =getNextAvail();11 int q = bitmapIdx >>> 6;12 int r = bitmapIdx & 63;13 assert (bitmap[q] >>> r & 1) == 0;14 bitmap[q] |= 1L <

16 if (-- numAvail == 0) {17 removeFromPool();18 }19

20 returntoHandle(bitmapIdx);21 }

第10行,得到下一个可用的subpage在bitmap中的索引bitmapIdx。

第11行,计算bitmapIdx在bitpmap数组中索引,q = bitmapIdx >>> 6 = (int)(bitmapIdx/64)。

第12行,计算bitmapIdx对应的bit在long数据中的位索引r,表示q对应的long数据的第r位就是。

第14行,把bitmapIdx对应的bit为设置为1。

第16,17行,把可用subpage数numAvail减1,如果numAvail==0表示当前PoolSubpage节点已经没有可用的subpage了,调用removeFromPool把它从链表中删除。

第20行,把bitmapIdx转换成表示内存的handle,算法是:  handle = 0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx;

释放一个subpage

free方法实现了subpage释放的功能,和allocate相比要简单的多,它的主要工作是把bitmapIdx对应的bit为设置为0,还顺便做了一下清理善后工作。

1 boolean free(PoolSubpage head, intbitmapIdx) {2 if (elemSize == 0) {3 return true;4 }5 int q = bitmapIdx >>> 6;6 int r = bitmapIdx & 63;7 assert (bitmap[q] >>> r & 1) != 0;8 bitmap[q] ^= 1L <

10 setNextAvail(bitmapIdx);11

12 if (numAvail ++ == 0) {13 addToPool(head);14 return true;15 }16

17 if (numAvail !=maxNumElems) {18 return true;19 } else{20 //Subpage not in use (numAvail == maxNumElems)

21 if (prev ==next) {22 //Do not remove if this subpage is the only one left in the pool.

23 return true;24 }25

26 //Remove this subpage from the pool if there are other subpages left in the pool.

27 doNotDestroy = false;28 removeFromPool();29 return false;30 }31 }

第5,6行,和allocate中解释过。

第8行,把bitmapIdx对应的bit为置为0。

第10行,把这个bitmapIdx赋值给nextAvail属性,这样在一次或多次调用free之后的第一次allocate调用就不会调用findNextAvail方法,可以提升allocate的性能。

第12,13行,当前PoolSubpage节点中至少有一个可用的subpage,把当前节点添加到链表中。

第27-29行,当前PooSubpage节点中所有分配出去的节点都全部还会来了,换言之,当前节点有回到bitmap初始化状态,把当前节点从链表中删除。

bytebuf池_netty源码解析(4.0)-26 ByteBuf内存池:PoolArena-PoolSubpage相关推荐

  1. 【Android 异步操作】线程池 ( 线程池作用 | 线程池种类 | 线程池工作机制 | 线程池任务调度源码解析 )

    文章目录 一.线程池作用 二.线程池种类 三.线程池工作机制 四.线程池任务调度源码解析 一.线程池作用 线程池作用 : ① 避免创建线程 : 避免每次使用线程时 , 都需要 创建线程对象 ; ② 统 ...

  2. Apache IoTDB源码解析(0.11.2版本):Session的源码解析

    1. 声明 当前内容主要为解析Apache IoTDB 0.11.2版本的Session的源码解析 通过前面的Apache Thrift的Demo,可以发现iotdb中的server是使用了thrif ...

  3. ceph bluestore源码分析:admin_socket实时获取内存池数据

    环境: 版本:ceph 12.2.1 部署完cephfs 使用ceph-fuse挂载,并写入数据 关键参数: debug_mempool = true 将该参数置为true即可查看详细的blustor ...

  4. netty获取玩家chanel_netty源码解析(4.0)-14 Channel NIO实现:读取数据

    本章分析Nio Channel的数据读取功能的实现. Channel读取数据需要Channel和ChannelHandler配合使用,netty设计数据读取功能包括三个要素:Channel, Even ...

  5. 【Android 异步操作】线程池 ( 线程池 execute 方法源码解析 )

    文章目录 一.线程池 execute 方法源码解析 二.线程池 execute 方法完整源码及注释 一.线程池 execute 方法源码解析 进入 ThreadPoolExecutor 中 , 查看线 ...

  6. 数值分析 使用c语言 源码_分析源码,学会正确使用 Java 线程池

    在日常的开发工作当中,线程池往往承载着一个应用中最重要的业务逻辑,因此我们有必要更多地去关注线程池的执行情况,包括异常的处理和分析等.本文主要聚焦在如何正确使用线程池上,以及提供一些实用的建议.文中会 ...

  7. balanced-match 源码解析

    balanced-match 源码解析 文章目录 balanced-match 源码解析 正文 0. 基本信息 0.1 Usage 0.2 Version: v2.0.0 0.2 Doc 1. 源码解 ...

  8. inflight 源码解析

    inflight 源码解析 文章目录 inflight 源码解析 正文 0. 基本信息 1. 源码解析 1.0 源码结构 1.1 主入口 inflight 1.2 启动函数创建着 makeres 其他 ...

  9. netty4.0源码解析(持续更新)

    文章目录 netty 1:环境配置 与其他对比 future缺陷 2:结构 3:设计模式 delay 策略模式 todo 责任链模式 todo 单例模式模式 todo 装饰者模式 ReplayingD ...

最新文章

  1. 试试这个文字冒险游戏,故事是AI写的:情节丰满逻辑不乱,进去就出不来了,在线可玩...
  2. 全字库说文解字字体_【180期】可商用字体大全,无版权纠纷!
  3. iOS从生成证书到打包上架-02(详细2016-10最新)
  4. 认识CSS中高级技巧之元素的显示与隐藏
  5. python如何导入类里_Python导入模块中的所有类(98)
  6. Tomcat部署到CentOS7
  7. 基于python的多光谱影像植被指数计算
  8. 中控考勤仪IFace302多线程操作时无法订阅事件
  9. 高帝制礼作乐,周昌谏废太子
  10. 【业务安全06】接口参数账号修改漏洞——基于metinfov4.0平台
  11. java实现发送国际短信的功能
  12. 项目推送到远程gitLab库时一般需要排除什么文件及其如何排除?
  13. 计算机发展史上代表性的人物,计算机发展史最具影响力人物
  14. 习题6-8 单词首字母大写 (15 分)
  15. Cocos2d-x 实现地图滚动,解释缝隙产生的原因以及解决方案
  16. Sentiment Word Aware Multimodal Refinement for MultimodalSentiment Analysis with ASR Errors
  17. x是偶数的c语言表达式,【单选题】能表示x 为偶数的表达式是 A. x%2==0 B. x%2==1 C. x-. x%2!=0...
  18. ZigBee——在CC2530的ZStack中添加定时任务
  19. 量化交易策略 alpha策略
  20. Anaconda python3.6版本

热门文章

  1. Linux错误27,解决在linux下编译32程序出现“/usr/include/gnu/stubs.h:7:27: 致命错误:gnu/stubs-32.h:没有那个文件或目录问题”...
  2. linux .o文件,Linux 文件I/O
  3. php不显示内容里的图片不显示,图片显示不出来,但是数据库里有显示
  4. java 代码块的意义_Java基础(9) - 静态、代码块
  5. php函数trim(),php trim函数怎么用?
  6. 怎么潜入别人家_小学生发明防雾口罩,别人家的孩子是怎么养成的?
  7. mysql的to datetime_mysql-笔记-datetime
  8. bt云服务器地址,windows2008搭建bttracker服务器
  9. linux 硬盘空间还有,但是无法创建文件
  10. 玩转 SpringBoot 2 之整合定时任务篇