前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Netty16# 池化内存Subpage类型内存分配

Netty16# 池化内存Subpage类型内存分配

作者头像
瓜农老梁
发布2021-04-22 10:07:19
5230
发布2021-04-22 10:07:19
举报
文章被收录于专栏:瓜农老梁

前言

前面聊了大于8KB的内存分配,那小于8KB的呢?上一篇的平衡二叉树第十一层的叶子节点最小也是8KB,那比如要分配128B的缓存,直接分给8KB显然是不合适的,Tiny是小于512Byte,Small介于512B~8KB,Tiny和Small统称Subpage,本文就聊聊他们的内存分配情况,这块应该是整个netty最为复杂的部分了。

内容提要

下面是以分配128B为例的整体流程架构图,下面大体叙述下其流程。

  • 先从平衡二叉树的第11层选一个未分配的叶子节点大小为8KB的一个Page 备注:本例中为memoryMap[2048]
  • 对该Page进行切割,假如要分配128B,整体会切割为64块 备注:8192/128=64
  • 通过long类型二进制64位来标记分割成各个块的分配状态 备注:0:未分配,1:已分配
  • 一个bitmap数组长度为8,每个元素都能对64块内存进行标记
  • 建立了二叉树节点与切分块之间的映射关系 备注:memoryMapIdx ^ maxSubpageAllocs
  • 分配后建立二叉树叶子节点与标记位之间的关系,可以指向内存一块区域 备注:0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx

源码分析

示例代码

代码语言:javascript
复制
@Test
public void testAllocateSubpage() {
    ByteBufAllocator allocator = new PooledByteBufAllocator();
    allocator.directBuffer(128); 
}

备注:以分配128B的内存为例,分析其分配过程。

源码分析

代码语言:javascript
复制
private long allocateSubpage(int normCapacity) {
    PoolSubpage<T> head = arena.findSubpagePoolHead(normCapacity); // 注解@1
   int d = maxOrder; 
    synchronized (head) {
        int id = allocateNode(d); // 注解@2
     if (id < 0) {
            return id;
        }
        final PoolSubpage<T>[] subpages = this.subpages; // 注解@3
        final int pageSize = this.pageSize;
        freeBytes -= pageSize;
        int subpageIdx = subpageIdx(id); // 注解@4

        PoolSubpage<T> subpage = subpages[subpageIdx]; 
        if (subpage == null) { // 注解@5
            subpage = new PoolSubpage<T>(head, this, id, runOffset(id), pageSize, normCapacity);
            subpages[subpageIdx] = subpage;
        } else {
            subpage.init(head, normCapacity);
        }
        return subpage.allocate(); // 注解@6
    }
}

注解@1 从tinySubpagePools中获取PoolSubpage。获取过程为elemSize >>> 4(除以16)来获取。

tinySubpagePools结构

tinySubpagePools被初始化成长度为32的数组,元素之间差额为16B。

注解@2 allocateNode 在上一篇文章分析过,d = maxOrder = 1。表示在平衡二叉树的第11层找到可分配的节点,具体为memoryMap数组中的下标。如果整个树都没有内存可分配了,返回的id=-1。

注解@3 先看下subpages的初始化,maxSubpageAllocs = 1 << maxOrder= 2048。也就是PoolSubpage[] subpages的长度为平衡二叉树第11层所有的节点数(2^11)。

代码语言:javascript
复制
subpages = newSubpageArray(maxSubpageAllocs);

注解@4 将平衡二叉树第11层的下标memoryMap[]的下标转换为subpages[]数组的下标。转换关系为memoryMapIdx ^ maxSubpageAllocs。

例如:平衡二叉树第11层第1个节点数组下标为2048,转换为subpages的下标为0,平衡二叉树第11层第2个节点数组下标为2049,转换为subpages的下标为1,平衡二叉树第11层第2个节点数组下标为2050,转换为subpages的下标为2。

注解@5 初始化PoolSubpage

代码语言:javascript
复制
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);
}

参数说明

代码语言:javascript
复制
head: PoolSubpage数组中的一个元素,本例中为第4个元素

chunk: 当前PoolChunk实例

memoryMapIdx: 平衡二叉树第11层用于分配的节点,具体为memoryMap数组下标

elemSize: 待分配的内存,本例中为128KB

bitmap: long数组长度为8「8192无符号右移10位=8」

初始化说明

代码语言:javascript
复制
void init(PoolSubpage<T> head, int elemSize) {
     doNotDestroy = true;
    // 待分配内存
     this.elemSize = elemSize; 
     if (elemSize != 0) {
            // maxNumElems表示可以被切割成几份(8192除以待分配内存)例如:64=8192/128被切成了64份
            maxNumElems = numAvail = pageSize / elemSize;
            nextAvail = 0;
            // 无符号右移6位,高位补零(相当于除以64)例如:64的二进制右移6位为1,128的二进制右移6位为2
            bitmapLength = maxNumElems >>> 6;
            if ((maxNumElems & 63) != 0) { // 相当于是否能被64整除
                bitmapLength ++; // 不能被整除递增bitmapLength
            }

            for (int i = 0; i < bitmapLength; i ++) {
                bitmap[i] = 0; // 等于零表示未被分配
            }
     }
     addToPool(head);
}

过程说明

@1 先计算一个Page被切成了几份 maxNumElems( pageSize / elemSize)

@2 计算bitmap数组长度bitmapLength(maxNumElems无符号右移6位相当于除以64)

备注: 此处不太好理解为什么要maxNumElems要除以64来计算bitmap的长度呢?也就是bitmap数组中的每个元素可以标记64个被切的内存块。bitmap是long数组,每个long类型是64位,他用每个二进制位来标记被切内存块的分配情况。

加入链表

新构建的PoolSubpage与tinySubpagePools中的PoolSubpage建成链表关系。

代码语言:javascript
复制
private void addToPool(PoolSubpage<T> head) {
    assert prev == null && next == null;
    prev = head;
    next = head.next;
    next.prev = this;
    head.next = this;
}

小结: 构造的PoolSubpage中持有了一个bitmap[]数组,数组长度与待分配的内存有关。待分配内存大小为elemSize,数组长度=PageSize/elemSize,并将bitmap数组的元素标记为未分配。

注解@6 分配内存

内存的分配以两次分配128B内存为例观察期分配过程。

代码语言:javascript
复制
@Test
public void testAllocateSubpage() {
   ByteBufAllocator allocator = new PooledByteBufAllocator();
   allocator.directBuffer(128); // 第一次分配
   allocator.directBuffer(128); // 第二次分配
}

第一次分配

第二次分配

第一次轮询第一位已被占用,需要向右移位。

第二次轮询第二位未被占用。

第二次分配过程

两次内存分配图示

第一次分配128B图示

此时64位第一位被标记为1,bitmap[0] = 1

第二次分配128B图示

此时64位第二位也被标记为1,bitmap[0] = 3

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 瓜农老梁 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 内容提要
  • 源码分析
  • 两次内存分配图示
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档