吃透Netty源码系列二十四之池化内存分配五

PoolChunk的initBuf

上一篇我们讲到子页的内存分配,讲到了我们获取了handle,今天将获取之后做什么。
在这里插入图片描述
我们可以看到子页分配的都是那么大的数,原因上篇已经讲过了。后面会有获取nioBuffer,就是在PoolChunk构造函数中创建的。
在这里插入图片描述
接下去下去就是initBuf方法了。

    void initBuf(PooledByteBuf<T> buf, ByteBuffer nioBuffer, long handle, int reqCapacity) {
        int memoryMapIdx = memoryMapIdx(handle);//低32位 即内存映射索引
        int bitmapIdx = bitmapIdx(handle);//这里获取的如果是子页的handle,bitmapIdx不为0,那高32位,并不是真正的位图索引,最高非符号位多了1,如果是normal的,那就是0
        if (bitmapIdx == 0) {//normal只有id,没有前面的子页相关的位图信息
            byte val = value(memoryMapIdx);
            assert val == unusable : String.valueOf(val);
            buf.init(this, nioBuffer, handle, runOffset(memoryMapIdx) + offset,
                    reqCapacity, runLength(memoryMapIdx), arena.parent.threadCache());
        } else {//子页初始化
            initBufWithSubpage(buf, nioBuffer, handle, bitmapIdx, reqCapacity);
        }
    }
    //获取低32位的handle,即id
    private static int memoryMapIdx(long handle) {
        return (int) handle;
    }
//直接获取handle高32位,子页的并非原始的bitmapIdx
    private static int bitmapIdx(long handle) {
        return (int) (handle >>> Integer.SIZE);
    }
    //获取对应编号的深度索引
    private byte value(int id) {
        return memoryMap[id];
    }

其实就是根据bitmapIdx来区分是子页的还是Normal的初始化。

PoolChunk的initBufWithSubpage

这里直接拿子页的讲,因为不是子页的也是调用了相同的buf.init方法,只是参数不一样。

  private void initBufWithSubpage(PooledByteBuf<T> buf, ByteBuffer nioBuffer,
                                    long handle, int bitmapIdx, int reqCapacity) {
        assert bitmapIdx != 0;

        int memoryMapIdx = memoryMapIdx(handle);//获取内存映射索引

        PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)];//获取对应的子页
        assert subpage.doNotDestroy;//还没销毁
        assert reqCapacity <= subpage.elemSize;//请求容量不会大于规范后的
        //池化字节缓冲区初始化
        buf.init(
            this, nioBuffer, handle,
            runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize + offset,//块的偏移地址+页的偏移地址+缓存行的偏移地址(默认0)
                reqCapacity, subpage.elemSize, arena.parent.threadCache());
    }

我们可以看到,子页会取出subpage ,因为下面会需要计算chunk块的偏移,也就是分配的内存在chunk块内部数组的其实地址。
runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize + offset我们来分析下这个地址什么意思。

runOffset(memoryMapIdx) 这个前面分析过,其实就是对应的页偏移地址,一页是8k嘛,也就是8k的整数倍。

(bitmapIdx & 0x3FFFFFFF) * subpage.elemSize这个里面的(bitmapIdx & 0x3FFFFFFF)就是对求handle时候用到的0x4000000000000000L解码啦,解码出子页真正的bitmapIdx,也就是子页内存的偏移位置,然后在乘以每块内存的大小subpage.elemSize,就是在子页中的偏移位置。

offset这个是缓存行的偏移,一般用不到,一个缓存行里放多个数据可能就要用到啦,不过这样可能会引起多线程竞争了,不过netty的这个内存分配方案,就是为了最大程度避免多线程的竞争,这个暂时就不多说了。

所以他们加起来就是在chunk块中的偏移,也就可以对应到chunk块中的memory上去了,最终都是字节数组。

我画个图吧,假设子页已经分配了64B的内存,即块上的字节数组用了64B,然后又申请了16B,最后的块偏移地址就是64,其实意思就是说从数组索引64开始,长度为16的分配给我用了。
在这里插入图片描述
如果对应的不是子页的话,就会少算一个子页内存的偏移,当然此时的分配大小就是runLength(memoryMapIdx),即某个节点的大小。
在这里插入图片描述

PooledByteBuf的init

最终会调用到init0

private void init0(PoolChunk<T> chunk, ByteBuffer nioBuffer,
                       long handle, int offset, int length, int maxLength, PoolThreadCache cache) {
        assert handle >= 0;
        assert chunk != null;

        this.chunk = chunk;//哪个块
        memory = chunk.memory;//字节数组,或者内存地址
        tmpNioBuf = nioBuffer;//缓存的buffer
        allocator = chunk.arena.parent;//分配器
        this.cache = cache;//线程缓存
        this.handle = handle;//句柄
        this.offset = offset;//缓存行偏移
        this.length = length;//申请的内存大小
        this.maxLength = maxLength;//规范化后的内存大小
    }

至此分配成功了,之后就返回:
在这里插入图片描述
需要把新的块加到块列表qInit中。其他的一些不重要的暂时不讲了。我们回顾下,整一个分配内存其实就是做了一件事,就是告诉我,我分配的内存在哪个块的字节数的哪个便宜位置上,可以用多少长度。

大致的流程讲完了,当然还有其他很多细节,我会慢慢补充的,下面来看下申请完了后,一些属性的变化。
看看这句代码执行后的情况:

ByteBuf byteBuf= ByteBufAllocator.DEFAULT.heapBuffer(5);

子页的一些属性:
在这里插入图片描述

PoolArena的allocateHuge

申请chunk块大小内的我们都讲了,现在讲下超过16MB的怎么处理的。

    private void allocateHuge(PooledByteBuf<T> buf, int reqCapacity) {
        PoolChunk<T> chunk = newUnpooledChunk(reqCapacity);//申请容量不用规划话,直接创建
        activeBytesHuge.add(chunk.chunkSize());//添加已用字节
        buf.initUnpooled(chunk, reqCapacity);
        allocationsHuge.increment();
    }

PoolArena的newUnpooledChunk

创建的是无池化的chunk

      @Override
        protected PoolChunk<byte[]> newUnpooledChunk(int capacity) {
            return new PoolChunk<byte[]>(this, newByteArray(capacity), capacity, 0);
        }

PoolChunk的无池化构造方法

其实就是好多池化内存管理的属性都用不到了,unpooled = true

  PoolChunk(PoolArena<T> arena, T memory, int size, int offset) {
        unpooled = true;
        this.arena = arena;
        this.memory = memory;
        this.offset = offset;
        memoryMap = null;
        depthMap = null;
        subpages = null;
        subpageOverflowMask = 0;
        pageSize = 0;
        pageShifts = 0;
        maxOrder = 0;
        unusable = (byte) (maxOrder + 1);
        chunkSize = size;
        log2ChunkSize = log2(chunkSize);
        maxSubpageAllocs = 0;
        cachedNioBuffers = null;
    }

PooledByteBuf的initUnpooled

最终还是调用init0,只是好多参数都是默认值,而且是没有缓存的。
在这里插入图片描述

申请内存的流程大致讲完了,后面还会讲下释放的时候是怎么样的,但是释放的时候会涉及到缓存,对象池,所以得先把缓存讲一下。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值