2023-09-13
原文作者:https://blog.csdn.net/wangwei19871103/category_9681495_2.html 原文地址: https://blog.csdn.net/wangwei19871103/article/details/104358738

PoolChunk的initBuf

上一篇我们讲到子页的内存分配,讲到了我们获取了handle,今天将获取之后做什么。

202309132202160631.png
我们可以看到子页分配的都是那么大的数,原因上篇已经讲过了。后面会有获取nioBuffer,就是在PoolChunk构造函数中创建的。

202309132202171562.png
接下去下去就是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的分配给我用了。

202309132202183373.png
如果对应的不是子页的话,就会少算一个子页内存的偏移,当然此时的分配大小就是runLength(memoryMapIdx),即某个节点的大小。

202309132202200964.png

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;//规范化后的内存大小
        }

至此分配成功了,之后就返回:

202309132202221555.png
需要把新的块加到块列表qInit中。其他的一些不重要的暂时不讲了。我们回顾下,整一个分配内存其实就是做了一件事,就是告诉我,我分配的内存在哪个块的字节数的哪个便宜位置上,可以用多少长度。

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

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

子页的一些属性:

202309132202230946.png

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,只是好多参数都是默认值,而且是 没有缓存 的。

202309132202239857.png

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

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

阅读全文