Netty分布式ByteBuf如何使用page级别的内存分配


这篇文章主要为大家展示了“Netty分布式ByteBuf如何使用page级别的内存分配”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Netty分布式ByteBuf如何使用page级别的内存分配”这篇文章吧。之前我们介绍过, netty内存分配的单位是chunk, 一个chunk的大小是16MB, 实际上每个chunk, 都以双向链表的形式保存在一个chunkList中, 而多个chunkList, 同样也是双向链表进行关联的, 大概结构如下所示:在chunkList中, 是根据chunk的内存使用率归到一个chunkList中, 这样, 在内存分配时, 会根据百分比找到相应的chunkList, 在chunkList中选择一个chunk进行内存分配这里总共定义了6个chunkList, 并在构造方法将其进行初始化跟到其构造方法中:首先通过new PoolChunkList()这种方式将每个chunkList进行创建, 我们以q050 =newPoolChunkList(q075, 50, 100, chunkSize)为例进行简单的介绍q075表示当前q50的下一个节点免费云主机域名是q075, 刚才我们讲过ChunkList是通过双向链表进行关联的, 所以这里不难理解参数50和100表示当前chunkList中存储的chunk的内存使用率都在50%到100%之间, 最后chunkSize为其设置大小创建完ChunkList之后, 再设置其上一个节点, q050.prevList(q025)为例, 这里代表当前chunkList的上一个节点是q025以这种方式创建完成之后, chunkList的节点关系变成了如下图所示:netty中, chunk又包含了多个page, 每个page的大小为8k, 如果要分配16k的内存, 则在在chunk中找到连续的两个page就可以分配, 对应关系如下:很多场景下, 为缓冲区分配8k的内存也是一种浪费, 比如只需要分配2k的缓冲区, 如果使用8k会造成6k的浪费, 这种情况, netty又会将page切分成多个subpage, 每个subpage大小要根据分配的缓冲区大小而指定, 比如要分配2k的内存, 就会将一个page切分成4个subpage, 每个subpage的大小为2k, 如图:chunk代表其子页属于哪个chunkbitmap用于记录子页的内存分配情况prev和next, 代表子页是按照双向链表进行关联的, 这里分别指向上一个和下一个节点elemSize属性, 代表的就是这个子页是按照多大内存进行划分的, 如果按照1k划分, 则可以划分出8个子页简单介绍了内存分配的数据结构, 我们开始剖析netty在page级别上分配内存的流程:我们之前讲过, 如果在缓存中分配不成功, 则会开辟一块连续的内存进行缓冲区分配, 这里我们先跳过isTinyOrSmall(normCapacity)往后的代码, 下一小节进行分析首先if(normCapacity
这里主要拆解了如下步骤1. 在原有的chunk中进行分配2. 创建chunk进行分配3. 初始化ByteBuf首先我们看第一步, 在原有的chunk中进行分配:我们之前讲过, chunkList是存储不同内存使用量的chunk集合, 每个chunkList通过双向链表的形式进行关联, 这里的q050.allocate(buf, reqCapacity, normCapacity)就代表首先在q050这个chunkList上进行内存分配我们以q050为例进行分析, 跟到q050.allocate(buf, reqCapacity, normCapacity)方法中:longhandle = cur.allocate(normCapacity)表示对于每个chunk, 都尝试去分配if(handle
如果handle大于0说明已经分配到了内存, 则通过cur.initBuf(buf, handle, reqCapacity)对byteBuf进行初始化if(cur.usage() >= maxUsage)代表当前chunk的内存使用率大于其最大使用率, 则通过remove(cur)从当前的chunkList中移除, 再通过nextList.add(cur)添加到下一个chunkList中我们再回到PoolArena的allocateNormal方法中:我们看第二步PoolChunk c = newChunk(pageSize, maxOrder, pageShifts, chunkSize)这里的参数pageSize是8192, 也就是8kmaxOrder为11pageShifts为13, 2的13次方正好是8192, 也就是8kchunkSize为16777216, 也就是16MB这里的参数值可以通过debug的方式跟踪到因为我们的示例是堆外内存, newChunk(pageSize, maxOrder, pageShifts, chunkSize)所以会走到DirectArena的newChunk方法中:allocateDirect(chunkSize)这里是通过jdk的api的申请了一块直接内存, 我们跟到PoolChunk的构造函数中:this.memory = memory就是将参数中创建的堆外内存进行保存, 就是chunk所指向的那块连续的内存, 在这个chunk中所分配的ByteBuf, 都会在这块内存中进行读写我们重点关注memoryMap =newbyte[maxSubpageAllocs
和depthMap =newbyte[memoryMap.length]这两步首先看memoryMap =newbyte[maxSubpageAllocs
这里初始化了一个字节数组memoryMap, 大小为maxSubpageAllocs
depthMap =newbyte[memoryMap.length]同样也是初始化了一个字节数组, 大小为memoryMap的大小, 也就是4096继续往下分析之前, 我们看chunk的一个层级关系这是一个二叉树的结构, 左侧的数字代表层级, 右侧代表一块连续的内存, 每个父节点下又拆分成多个子节点, 最顶层表示的内存范围为0-16MB, 其又下分为两层, 范围为0-8MB, 8-16MB, 以此类推, 最后到11层, 以8k的大小划分, 也就是一个page的大小如果我们分配一个8mb的缓冲区, 则会将第二层的第一个节点, 也就是0-8这个连续的内存进行分配, 分配完成之后, 会将这个节点设置为不可用, 具体逻辑后面会讲解结合上面的图, 我们再看构造方法中的for循环:实际上这个for循环就是将上面的结构包装成一个字节数组memoryMap, 外层循环用于控制层数, 内层循环用于控制里面每层的节点, 这里经过循环之后, memoryMap和depthMap内容为以下表现形式:[0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4………..]这里注意一下, 因为程序中数组的下标是从1开始设置的, 所以第零个节点元素为默认值0这里数字代表层级, 同时也代表了当前层级的节点, 相同的数字个数就是这一层级的节点数其中0为2个(因为这里分配时下标是从1开始的, 所以第0个位置是默认值0, 实际上第零层元素只有一个, 就是头结点), 1为2个, 2为4个, 3为8个, 4为16个, n为2的n次方个, 直到11, 也就是11有2的11次方个我们继续剖析longhandle = c.allocate(normCapacity)这步如果分配是以page为单位, 则走到allocateRun(normCapacity)方法中, 跟进去:intd = maxOrder – (log2(normCapacity) – pageShifts)表示根据normCapacity计算出图5-8-5中的第几层intid = allocateNode(d)表示根据层级关系, 去分配一个节点, 其中id代表memoryMap中的下标这里是实际上是从第一个节点往下找, 找到层级为d未被使用的节点, 我们可以通过注释体会其逻辑找到相关节点后通过setValue将当前节点设置为不可用, 其中id是当前节点的下标, unusable代表一个不可用的值, 这里是12, 因为我们的层级只有12层, 所以设置为12之后就相当于标记不可用设置成不可用之后, 通过updateParentsAlloc(id)逐层设置为被使用

相关推荐: Java中的函数式编程怎么使用

本文小编为大家详细介绍“Java中的函数式编程怎么使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java中的函数式编程怎么使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。函数式编程的理论基础是阿隆佐丘奇(Alonzo Chu…

免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。

Like (0)
Donate 微信扫一扫 微信扫一扫
Previous 07/24 11:48
Next 07/24 11:48

相关推荐