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<T>[]数组的形式保存:

  • tinySubpagePools:用于分配Tiny内存,数组长度是521 >> 4 = 32。
  • smallSubpagePools: 用于分配Small内存,数组长度是pageShifts - 9。

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

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

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

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

     PoolSubpage<T> findSubpagePoolHead(int elemSize) {
int tableIdx;
PoolSubpage<T>[] table;
if (isTiny(elemSize)) { // < 512
tableIdx = elemSize >>> 4;
table = tinySubpagePools;
} else {
tableIdx = 0;
elemSize >>>= 10;
while (elemSize != 0) {
elemSize >>>= 1;
tableIdx ++;
}
table = smallSubpagePools;
} return table[tableIdx];
}

  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方法进行初始化。

     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;
bitmap = new long[pageSize >>> 10]; // pageSize / 16 / 64
init(head, elemSize);
}

  这个构造方法只是做了一些简单的属性初始化工作。第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。

     void init(PoolSubpage<T> head, int elemSize) {
doNotDestroy = true;
this.elemSize = elemSize;
if (elemSize != 0) {
maxNumElems = numAvail = pageSize / elemSize;
nextAvail = 0;
bitmapLength = maxNumElems >>> 6;
if ((maxNumElems & 63) != 0) {
bitmapLength ++;
} for (int i = 0; i < bitmapLength; i ++) {
bitmap[i] = 0;
}
}
addToPool(head);
}

  第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有两个步骤:

  1. 找到一个可用subpage的索引bitmapIdx
  2. 把这个bitmapIdx在bitmap中对应的bit为置为1

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

     private int findNextAvail() {
final long[] bitmap = this.bitmap;
final int bitmapLength = this.bitmapLength;
for (int i = 0; i < bitmapLength; i ++) {
long bits = bitmap[i];
if (~bits != 0) {
return findNextAvail0(i, bits);
}
}
return -1;
} private int findNextAvail0(int i, long bits) {
final int maxNumElems = this.maxNumElems;
final int baseVal = i << 6; for (int j = 0; j < 64; j ++) {
if ((bits & 1) == 0) {
int val = baseVal | j;
if (val < maxNumElems) {
return val;
} else {
break;
}
}
bits >>>= 1;
}
return -1;
}

  第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。

     long allocate() {
if (elemSize == 0) {
return toHandle(0);
} if (numAvail == 0 || !doNotDestroy) {
return -1;
} final int bitmapIdx = getNextAvail();
int q = bitmapIdx >>> 6;
int r = bitmapIdx & 63;
assert (bitmap[q] >>> r & 1) == 0;
bitmap[q] |= 1L << r; if (-- numAvail == 0) {
removeFromPool();
} return toHandle(bitmapIdx);
}

  第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,还顺便做了一下清理善后工作。

     boolean free(PoolSubpage<T> head, int bitmapIdx) {
if (elemSize == 0) {
return true;
}
int q = bitmapIdx >>> 6;
int r = bitmapIdx & 63;
assert (bitmap[q] >>> r & 1) != 0;
bitmap[q] ^= 1L << r; setNextAvail(bitmapIdx); 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;
removeFromPool();
return false;
}
}

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

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

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

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

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

netty源码解析(4.0)-26 ByteBuf内存池:PoolArena-PoolSubpage的更多相关文章

  1. netty源码解析(4.0)-27 ByteBuf内存池:PoolArena-PoolThreadCache

    前面两章分析的PoolChunk和PoolSubpage,从功能上来说已经可以直接拿来用了.但直接使用这个两个类管理内存在高频分配/释放内存场景下会有性能问题,PoolChunk分配内存时算法复杂度最 ...

  2. netty源码解析(4.0)-28 ByteBuf内存池:PooledByteBufAllocator-把一切组装起来

    PooledByteBufAllocator负责初始化PoolArena(PA)和PoolThreadCache(PTC).它提供了一系列的接口,用来创建使用堆内存或直接内存的PooledByteBu ...

  3. netty源码解析(4.0)-21 ByteBuf的设计原理

        io.netty.buffer包中是netty ByteBuf的实现.ByteBuf是一个二进制缓冲区的抽象接口,它的功能有: 可以随机访问.顺序访问. 支持基本数据类型(byte, shor ...

  4. netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk

    PoolArena实现了用于高效分配和释放内存,并尽可能减少内存碎片的内存池,这个内存管理实现使用PageRun/PoolSubpage算法.分析代码之前,先熟悉一些重要的概念: page: 页,一个 ...

  5. netty源码解析(4.0)-29 Future模式的实现

    Future模式是一个重要的异步并发模式,在JDK有实现.但JDK实现的Future模式功能比较简单,使用起来比较复杂.Netty在JDK Future基础上,加强了Future的能力,具体体现在: ...

  6. Netty源码分析第5章(ByteBuf)---->第2节: ByteBuf的分类

    Netty源码分析第五章: ByteBuf 第二节: ByteBuf的分类 上一小节简单介绍了AbstractByteBuf这个抽象类, 这一小节对其子类的分类做一个简单的介绍 ByteBuf根据不同 ...

  7. Netty源码分析第5章(ByteBuf)---->第1节: AbstractByteBuf

    Netty源码分析第五章: ByteBuf 概述: 熟悉Nio的小伙伴应该对jdk底层byteBuffer不会陌生, 也就是字节缓冲区, 主要用于对网络底层io进行读写, 当channel中有数据时, ...

  8. Netty源码分析第5章(ByteBuf)---->第3节: 缓冲区分配器

    Netty源码分析第五章: ByteBuf 第三节: 缓冲区分配器 缓冲区分配器, 顾明思议就是分配缓冲区的工具, 在netty中, 缓冲区分配器的顶级抽象是接口ByteBufAllocator, 里 ...

  9. Netty源码分析第5章(ByteBuf)---->第4节: PooledByteBufAllocator简述

    Netty源码分析第五章: ByteBuf 第四节: PooledByteBufAllocator简述 上一小节简单介绍了ByteBufAllocator以及其子类UnPooledByteBufAll ...

随机推荐

  1. [翻译] ASP.NET Core 3.0 的新增功能

    ASP.NET Core 3.0 的新增功能 全文翻译自微软官方文档英文版 What's new in ASP.NET Core 3.0 本文重点介绍了 ASP.NET Core 3.0 中最重要的更 ...

  2. springboot 集成Redis单机

    1.redis服务搭建 centos7 搭建redis服务 2.接入相关 pom文件依赖引入 <dependencies> <dependency> <groupId&g ...

  3. linux 设置查看文本行数

    在一般模式下,即摁下esc按键下的模式: 设置行数为:set nu(此处的冒号需要带上) 取消行号为:set nonu(此处的冒号需要带上)

  4. jQuery三级联动效果代码(省、市、区)

    很长时间都不用jquery了,有人问我jquery写三级联动的插件我就写好了发出来吧,正好需要的人都可以看看. 一.html代码 <!DOCTYPE html> <html> ...

  5. 如何编写高质量的 JS 函数(3) --函数式编程[理论篇]

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/EWSqZuujHIRyx8Eb2SSidQ作者:杨昆 [编写高质量函数系列]中, <如何 ...

  6. 我的mongoDb之旅(二)

    题序:上一回,咱们简单的操作了一些增减改查的操作,这一次,再来进行一场奇妙之旅 一.案例讲解 (1).字段有值与没值时的单条数据 第一条数据title这个字段(mysql用久了,习惯这么叫了)是没有数 ...

  7. 为什么要学习go语言

    终于等到你!Go语言--让你用写Python代码的开发效率编写C语言代码. 为什么互联网世界需要Go语言 世界上已经有太多太多的编程语言了,为什么又出来一个Go语言? 硬件限制:摩尔定律已然失效 摩尔 ...

  8. Creator3D长什么样?看看官方惊艳的DEMO就知道了,附在线体验!

    Shawn 这两天在学习 Creator3D 的官方案例,由于是刚接触 Creator3D 很多东西在没弄清楚之前还是以简单的编辑介绍为主,先了解一下3D场景的基本操作: 观查场景:按住鼠标右键以自己 ...

  9. [LeetCode] 1137. N-th Tribonacci Number

    Description e Tribonacci sequence Tn is defined as follows: T0 = 0, T1 = 1, T2 = 1, and Tn+3 = Tn + ...

  10. MongoDB 学习笔记之 DBRef

    DBRef: MongoDB建模有两种方式,一种是内嵌(Embed),另一种是连接(Link).内嵌比较好理解,就是字段内容是个数组,数组内再包含文档,而我们今天介绍的是另一种,称为链接DBRef.由 ...