netty源码阅读之ByteBuf之内存subpage级别内存的分配

38 篇文章 1 订阅
33 篇文章 1 订阅

subpage级别的内存分配的主要方法为:allocateTiny()。

主要分为以下三个步骤:

1、定位一个subpage对象

2、初始化subpage

3、初始化pooledByteBuf

这一次,我们的用户代码是分配16B的内存

public class Test {
    public static void main(String[] args) {
        PooledByteBufAllocator allocator = PooledByteBufAllocator.DEFAULT;
        allocator.directBuffer(16);
    }
}

一、定位一个subpage对象 

毫无疑问,tiny加上第一次分配不会有cache的,我们走到了这里:

我们看tinyIdx的实现:

    static int tinyIdx(int normCapacity) {
        return normCapacity >>> 4;
    }

也就是,normCapacity向右移动4位。

然后把tinySubpagePools赋给table。这个是什么意思呢?先看tinySubpagePools的构成,其实和之前MemoryRegionCache的tiny类型的组成类似,是由一个从0到496b的数组组成的。

tinySubpagePools(在arena里面):

tiny[32]={0,16B,32B,48B,...,480B,496B} 

由于tinySubpagePools是以0为首项,16为公差的等差数列。16右移4位也就是16除以16就是1,所以取tinySubpagePools的第二位,也就是下标为1,对的。

然后继续step over

我们取到table的index为tableIdx的tiny数组的节点也就是,tinySubpagePools的第二个节点,容量为16。

继续往下走,

s!=head这个条件是不满足的,我们会直接走到allocateNormal。

这里先解释一下,我们知道jdk1.7之前,hashmap的组成是数组加链表。这个tinySubpagePools也是一样的。现在index为1的位置只有一个节点,head.next还是自己,所以不走这里条件里面。继续看allocateNormal(buf,reqCapacity,normCapacity):

流程和page的是一样的,不过在c.allocate这里有所不同,进入:

这一次就进入了allocateSubpage,上一篇是allocateRun(),然后进入allocateSubpage():

从第11层开始进入,所以取maxOrder,有一段注释:

subpages are only be allocaed from pages i.e.,leaves d:11

这里就是说,subpage只能从叶子节点也就是8k为单位的节点分配,我们又拿这个图来:

也就是在8k的这些节点里面分配subpage,说得很明白: 

在memoryMap里面,第2048个节点是能够分配subpage的,验证了我们的说法。 

继续往下走:

我们刚刚说过了tinySubpagePools是数组加链表的方式,我们使用的是index为1的节点,这个节点刚开始是没有数的,所以subpage为空,所以进去新建一个:

我们看PoolSubpage的构造函数:

我们首先要给chunk赋值,让PoolSubpage知道自己属于哪个chunk。然后属于memoryMapIdx里面的第几个memoryMapIdx,偏移量是多少,pageSize的大小为8192也就是8k。

我们注意到elemSize=16,也就是我们要分配的内存大小,进入init:

刚刚说的elemSize在这里用上了,根据需要分配的大小,把pageSize分配为多少等分:

maxNumElems=numAvail=pageSize/elemSize;

然后它的bitmap也进行初始化,未使用就标记为0,使用就标记为1,这里是初始化,所以都是未使用,都为0。

最后有一个addToPool(head)函数:


    private void addToPool(PoolSubpage<T> head) {
        assert prev == null && next == null;
        prev = head;
        next = head.next;
        next.prev = this;
        head.next = this;
    }

也就是,把这个节点和head节点组成双向链表。

 

二、初始化subpage

回来这里:

进入allocate方法:

上面的那些操作就是通过一定的算法找到bitmapIdx,toHandle(bitmapIdx)这个方法很关键:

    private long toHandle(int bitmapIdx) {
        return 0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx;
    }

这里的操作就是bitmapIdx左移32位,或上0x4000000000000000,最后或上memoryMapIdx。

也就是吧memoryMapIdx作为低32位,bitmapIdx作为高32位,拼接起来。

memoryMapIdx就是chunk里面page的位置,或上0x4000000000000000的时候,在上一篇文章bitmapIdx(handle)是的时候,得到的值才能不为0。

拿到的这个handle就是:在一个chunk里面第几个节点,然后节点的第几个subpage,也就是能够拿到一块内存里面的哪一块连续内存。

拿到了之后,就给ByteBuf进行初始化:

 

三、初始化pooledByteBuf

最后回到这里:

进去:

刚刚说的,若是subpage的,handle为4开头的大数,bitmapIdx()之后,bitmapIdx不为0,所以等下会走initBufWithSubpage这个方法:

然后继续进入:

我们看runOffset(memoryMapIdx)+(bitmapIdx&0X3FFFFFFF)*subpage.elemSize这个表达式的值:

runOffset(memoryMapIdx)因为没有偏移量,所以为0(因为是page的第一个节点,page没有偏移);(bitmapIdx&0X3FFFFFFF)为page的初始位置,如果现在是page里面的第1块,所以(bitmapIdx&0X3FFFFFFF)为0,如果是第2块,(bitmapIdx&0X3FFFFFFF)为1,乘上subpage.elemSize也就是16b之后,偏移量也是对的。

page的偏移加上subpage的偏移,最终就是真个内存的偏移量。把这个偏移量传递给buf,那buf在进行内存读写的时候,就可以基于这个偏移量进行内存的读写,通过这些值去初始化buf。

最后,我们就把这个流程走完了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值