首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Netty源码—7.ByteBuf原理三

Netty源码—7.ByteBuf原理三

原创
作者头像
东阳马生架构
发布2025-06-13 08:53:09
发布2025-06-13 08:53:09
1650
举报
文章被收录于专栏:Netty应用与源码Netty应用与源码

大纲

9.Netty的内存规格

10.缓存数据结构

11.命中缓存的分配流程

12.Netty里有关内存分配的重要概念

13.Page级别的内存分配

14.SubPage级别的内存分配

15.ByteBuf的回收

14.SubPage级别的内存分配

(1)SubPage级别的内存分配的入口

(2)SubPage级别的内存分配的流程

(3)定位一个SubPage对象

(4)初始化SubPage对象

(5)调用subpage.allocate()进行分配

(1)SubPage级别的内存分配的入口

下面这3行代码可以用来跟踪进行SubPage级别内存分配时的调用栈:

代码语言:javascript
复制
PooledByteBufAllocator allocator = PooledByteBufAllocator.DEFAULT;
ByteBuf byteBuf = allocator.directBuffer(16);//分配16B内存
byteBuf.release();

PooledByteBufAllocator的directBuffer()方法通过其newDirectBuffer()方法执行代码directArena.allocate()时,首先会调用PoolArena的allocate()方法,然后会调用PoolArena的allocateNormal()方法,接着会调用PoolChunk的allocate()方法,并最终调用到PoolChunk的allocateSubpage()方法进行SubPage级别的内存分配。进行SubPage级别的内存分配时不会分配多个Page而只会分配一个Page的一部分。

在PoolArena的allocate()方法中,首先会通过tinyIdx(16)拿到tableIdx,此时tableIdx = 16 >>> 4 = 1。然后从PoolArena的tinySubpagePools数组中取出下标为tableIdx的一个PoolSubpage元素赋值给table。tinySubpagePools默认和MemoryRegionCache的缓存一样,也有32个元素:16B、32B、48B、...、480、496B。其中第n个元素表示大小为(n - 1) * 16B的专属SubPage。接着通过PoolArena.allocateNormal()方法调用到PoolChunk.allocateSubpage()方法。

代码语言:javascript
复制
abstract class PoolArena<T> implements PoolArenaMetric {
    //有32个元素:16B、32B、48B、...、480、496B
    private final PoolSubpage<T>[] tinySubpagePools;
    //有4个元素:512B、1K、2K、4K
    private final PoolSubpage<T>[] smallSubpagePools;
    ...    
    PooledByteBuf<T> allocate(PoolThreadCache cache, int reqCapacity, int maxCapacity) {
        PooledByteBuf<T> buf = newByteBuf(maxCapacity);//创建ByteBuf对象
        allocate(cache, buf, reqCapacity);//基于PoolThreadCache对ByteBuf对象进行内存分配
        return buf;
    }
    
    private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
        //1.根据reqCapacity进行分段规格化
        final int normCapacity = normalizeCapacity(reqCapacity);
        if (isTinyOrSmall(normCapacity)) {//capacity < pageSize,需要分配的内存小于8K
            int tableIdx;
            PoolSubpage<T>[] table;
            boolean tiny = isTiny(normCapacity);
            if (tiny) {//capacity < 512
                if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
                    //was able to allocate out of the cache so move on
                    return;
                }
                //根据规格化后的需要分配的内存大小normCapacity,获取tableIdx
                tableIdx = tinyIdx(normCapacity);
                table = tinySubpagePools;
            } else {
                if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) {
                    //was able to allocate out of the cache so move on
                    return;
                }
                //根据规格化后的需要分配的内存大小normCapacity,获取tableIdx
                tableIdx = smallIdx(normCapacity);
                table = smallSubpagePools;
            }

            final PoolSubpage<T> head = table[tableIdx];

            //Synchronize on the head. 
            //This is needed as PoolChunk#allocateSubpage(int) and PoolChunk#free(long) may modify the doubly linked list as well. 
            synchronized (head) {
                final PoolSubpage<T> s = head.next;
                if (s != head) {//PoolArena的tinySubpagePools数组中有可以分配内存的PoolSubpage
                    assert s.doNotDestroy && s.elemSize == normCapacity;
                    //调用SubPage的allocate()方法进行内存分配
                    long handle = s.allocate();
                    assert handle >= 0;
                    s.chunk.initBufWithSubpage(buf, handle, reqCapacity);

                    if (tiny) {
                        allocationsTiny.increment();
                    } else {
                        allocationsSmall.increment();
                    }
                    return;
                }
            }
            allocateNormal(buf, reqCapacity, normCapacity);
            return;
        }
        ...
    }
    
    static int tinyIdx(int normCapacity) {
        return normCapacity >>> 4;
    }

    static int smallIdx(int normCapacity) {
        int tableIdx = 0;
        int i = normCapacity >>> 10;
        while (i != 0) {
            i >>>= 1;
            tableIdx ++;
        }
        return tableIdx;
    }
    
    private synchronized void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
        //1.尝试在现有的PoolChunk上分配
        if (q050.allocate(buf, reqCapacity, normCapacity) || q025.allocate(buf, reqCapacity, normCapacity) ||
            q000.allocate(buf, reqCapacity, normCapacity) || qInit.allocate(buf, reqCapacity, normCapacity) ||
            q075.allocate(buf, reqCapacity, normCapacity)) {
            ++allocationsNormal;
            return;
        }

        //2.创建一个PoolChunk并进行内存分配
        PoolChunk<T> c = newChunk(pageSize, maxOrder, pageShifts, chunkSize);
        //由handle指向PoolChunk里的一块连续内存
        long handle = c.allocate(normCapacity);
        ++allocationsNormal;
        assert handle > 0;
        //3.初始化PooledByteBuf对象
        c.initBuf(buf, handle, reqCapacity);
        //4.将新建的PoolChunk添加到PoolArena的qInit这个PoolChunkList中
        qInit.add(c);
    }
    ...
}

final class PoolChunk<T> implements PoolChunkMetric {
    ...
    long allocate(int normCapacity) {
        if ((normCapacity & subpageOverflowMask) != 0) {//normCapacity >= pageSize
            //Page级别的内存分配
            return allocateRun(normCapacity);
        } else {
            //SubPage级别的内存分配
            return allocateSubpage(normCapacity);
        }
    }
    ...
}

(2)SubPage级别的内存分配的流程

PoolChunk.allocateSubpage()方法的主要操作:

一.定位一个SubPage对象

二.初始化SubPage对象

三.调用SubPage的allocate()方法进行分配

代码语言:javascript
复制
final class PoolChunk<T> implements PoolChunkMetric {
    final PoolArena<T> arena;
    final T memory;//内存
    //一个Page的大小,比如8K
    private final int pageSize;
    //4096个元素的字节数组,表示不同规格的连续内存使用分配情况,用二叉树理解
    private final byte[] memoryMap;
    private final byte[] depthMap;
    //2048个元素的数组,表示Chunk里哪些Page是以SubPage方式存在的
    //由于一个PoolChunk是16M,会以8K为标准划分一个个的Page,所以会有16 * 1024 / 8 = 2048个Page
    private final PoolSubpage<T>[] subpages;
    //Used to mark memory as unusable
    private final byte unusable;//默认是12
    ...
    //Create/ initialize a new PoolSubpage of normCapacity
    //Any PoolSubpage created/ initialized here is added to subpage pool in the PoolArena that owns this PoolChunk
    //@param normCapacity normalized capacity
    //@return index in memoryMap
    private long allocateSubpage(int normCapacity) {
        //Obtain the head of the PoolSubPage pool that is owned by the PoolArena and synchronize on it.
        //This is need as we may add it back and so alter the linked-list structure.
        //1.定位一个SubPage对象
        //PoolArena的findSubpagePoolHead()方法会通过除以16,来获取用来存放16B的PoolSubpage节点
        PoolSubpage<T> head = arena.findSubpagePoolHead(normCapacity);
        synchronized (head) {
            //只能从层高为11的memoryMap中获取SubPage,因为只有这一层的每个结点都是8K
            int d = maxOrder;//subpages are only be allocated from pages i.e., leaves
            //1.定位一个SubPage对象:在平衡二叉树的第11层上分配一个结点
            //即通过allocateNode(d)找到一个Page在PoolChunk中的下标idx
            int id = allocateNode(d);
            if (id < 0) {
                return id;
            }

            final PoolSubpage<T>[] subpages = this.subpages;
            final int pageSize = this.pageSize;
            freeBytes -= pageSize;
           
            //1.定位一个SubPage对象:确定PoolChunk中第几个Page会以SubPage方式存在
            int subpageIdx = subpageIdx(id);
            PoolSubpage<T> subpage = subpages[subpageIdx];
            //2.初始化SubPage对象
            if (subpage == null) {
                subpage = new PoolSubpage<T>(head, this, id, runOffset(id), pageSize, normCapacity);
                subpages[subpageIdx] = subpage;
            } else {
                subpage.init(head, normCapacity);
            }
            //3.调用SubPage的allocate()方法进行分配
            return subpage.allocate();
        }
    }
    ...
}

(3)定位一个SubPage对象

SubPage是基于一个Page进行划分的,不管是从一个现有的SubPage对象中分配,还是在没有SubPage对象时创建一个SubPage,第一个步骤都是定位一个SubPage对象。

在PoolChunk的allocateSubpage()方法中:首先会通过PoolArena的findSubpagePoolHead()方法去找到在PoolArena的tinySubpagePools中用于存放16B的PoolSubpage结点。然后在连续内存的平衡二叉树的第11层上分配一个Page结点,即通过allocateNode(d)找到一个Page在PoolChunk中的下标id。接着通过subpageIdx(id)确定PoolChunk中第subpageIdx个Page会以SubPage方式存在,从而定位到在PoolChunk的subpages数组中下标为subpageIdx对应的PoolSubpage元素可用来进行SubPage级别的内存分配。

注意:在PoolChunk的subpages数组中,如果某个下标对应的PoolSubpage元素为空,则说明这个下标对应的PoolChunk中的某个Page已经用来进行了Page级别的内存分配或者还没被分配。

代码语言:javascript
复制
abstract class PoolArena<T> implements PoolArenaMetric {
    //有32个元素:16B、32B、48B、...、480、496B
    private final PoolSubpage<T>[] tinySubpagePools;
    //有4个元素:512B、1K、2K、4K
    private final PoolSubpage<T>[] smallSubpagePools;
    ...
    //找到在PoolArena的tinySubpagePools中用于存放16B的PoolSubpage结点
    PoolSubpage<T> findSubpagePoolHead(int elemSize) {
        int tableIdx;
        PoolSubpage<T>[] table;
        if (isTiny(elemSize)) {//< 512
            tableIdx = elemSize >>> 4;
            table = tinySubpagePools;
        } else {
            tableIdx = 0;
            elemSize >>>= 10;
            while (elemSize != 0) {
                elemSize >>>= 1;
                tableIdx ++;
            }
            table = smallSubpagePools;
        }
        return table[tableIdx];
    }
    ...
}

final class PoolChunk<T> implements PoolChunkMetric {
    ...
    //Algorithm to allocate an index in memoryMap when we query for a free node at depth d
    //@param d depth
    //@return index in memoryMap
    private int allocateNode(int d) {
        int id = 1;
        int initial = - (1 << d);//has last d bits = 0 and rest all = 1
        //取出memoryMap[id]的值
        byte val = value(id);
        if (val > d) {//val = unusable = 12
            return -1;
        }
        //val < d表示当前结点可用
        while (val < d || (id & initial) == 0) {//id & initial == 1 << d for all ids at depth d, for < d it is 0
            id <<= 1;//每循环一次乘以2
            val = value(id);
            if (val > d) {//如果当前id对应的结点不可用
                id ^= 1;//通过异或运算实现对id加1
                val = value(id);
            }
        }
        byte value = value(id);
        assert value == d && (id & initial) == 1 << d : String.format("val = %d, id & initial = %d, d = %d", value, id & initial, d);
        setValue(id, unusable);//mark as unusable = 12
        updateParentsAlloc(id);//逐层往上标记结点不可用
        return id;
    }
    
    private int subpageIdx(int memoryMapIdx) {
        return memoryMapIdx ^ maxSubpageAllocs;//remove highest set bit, to get offset
    }
    ...
}

(4)初始化SubPage对象

如果PoolChunk的subpages数组中下标为subpageIdx的PoolSubpage元素为空,那么就会创建一个PoolSubpage对象并对其进行初始化。初始化的过程就是去一个PoolChunk里寻找一个Page,然后按照SubPage大小将该Page进行划分。当完成PoolSubpage对象的初始化之后,就可以通过它的allocate()方法来进行内存分配了。具体来说就是把内存信息设置到PooledByteBuf对象中。

代码语言:javascript
复制
final class PoolSubpage<T> implements PoolSubpageMetric {
    final PoolChunk<T> chunk;
    private final int memoryMapIdx;
    private final int runOffset;
    private final int pageSize;
    private final long[] bitmap;
    ...
    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);
    }

    void init(PoolSubpage<T> head, int elemSize) {
        doNotDestroy = true;
        this.elemSize = elemSize;
        if (elemSize != 0) {
            maxNumElems = numAvail = pageSize / elemSize;
            nextAvail = 0;
            bitmapLength = maxNumElems >>> 6;
            if ((maxNumElems & 63) != 0) {
                bitmapLength ++;
            }
            for (int i = 0; i < bitmapLength; i ++) {
                bitmap[i] = 0;
            }
        }
        //将当前PoolSubpage对象添加到PoolArena的tinySubpagePools数组中用于存放
        //可以分配16B内存的PoolSubpage链表中
        addToPool(head);
    }
    
    private void addToPool(PoolSubpage<T> head) {
        assert prev == null && next == null;
        prev = head;
        next = head.next;
        next.prev = this;
        head.next = this;
    }
    ...
}

PoolSubpage的构造方法调用init()方法的处理:

一.设置elemSize用于表示一个SubPage的大小,比如规格化后所申请的内存大小:16B、32B等。

二.设置bitmap用于标识把一个Page划分成多个SubPage后哪一个SubPage已被分配,0表示未分配,1表示已分配。

三.通过addToPool()方法把当前PoolSubpage对象添加到PoolArena的tinySubpagePools数组中可以分配某种规格大小内存的PoolSubpage链表里。

(5)调用SubPage的allocate()方法进行分配

首先从位图bitmap里寻找一个未被使用的SubPage。如果可用的SubPage的数量为0,则直接把该PoolSubpage对象从PoolArena的tinySubpagePools数组的某种规格的结点中移除。接着将代表未使用SubPage的bitmapIdx转换成handle,也就是拼接成64位 + bitmapIdx变成高32位 + memoryMapIdx变成低32位,所得的handle便表示一个PoolChunk里第几个Page的第几个SubPage,从而可以拿到连续内存给PooledByteBuf进行初始化。

代码语言:javascript
复制
final class PoolSubpage<T> implements PoolSubpageMetric {
    int elemSize;
    private final long[] bitmap;
    private int numAvail;
    private final int memoryMapIdx;
    ...
    //Returns the bitmap index of the subpage allocation.
    long allocate() {
        if (elemSize == 0) {
            return toHandle(0);
        }
        if (numAvail == 0 || !doNotDestroy) {
            return -1;
        }
        //1.先从位图bitmap中寻找一个未被使用的SubPage
        final int bitmapIdx = getNextAvail();
        int q = bitmapIdx >>> 6;
        int r = bitmapIdx & 63;
        assert (bitmap[q] >>> r & 1) == 0;
        bitmap[q] |= 1L << r;
      
        //如果可用的SubPage为0
        if (-- numAvail == 0) {
            //把该PoolSubpage对象从PoolArena的tinySubpagePools数组的某种规格的结点中移除
            removeFromPool();
        }
        //2.将bitmapIdx转换成handle
        return toHandle(bitmapIdx);
    }
    
    private long toHandle(int bitmapIdx) {
        //拼接成64位,bitmapIdx变成高32位,memoryMapIdx变成低32位
        return 0x4000000000000000L | (long) bitmapIdx << 32 | memoryMapIdx;
    }
    ...
}

15.ByteBuf的回收

(1)池化的内存如何释放

(2)将连续内存的区段加到缓存

(3)标记连续内存的区段为未使用

(4)将ByteBuf对象添加到对象池

(1)池化的内存如何释放

比如byteBuf.release()会调用到PooledByteBuf的deallocate()方法。该方法首先会清空PooledByteBuf对象的handle、chunk、memory变量值。然后调用PoolArena的free()方法去释放对应PoolChunk在handle处的内存,也就是将连续内存的区段添加到缓存 + 标记连续内存的区段为未使用。接着调用PooledByteBuf的recycle()方法去复用PooledByteBuf对象。

代码语言:javascript
复制
public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {
    ...
    //byteBuf执行release()方法释放内存
    @Override
    public boolean release() {
        return release0(1);
    }
    
    private boolean release0(int decrement) {
        for (;;) {
            int refCnt = this.refCnt;
            if (refCnt < decrement) {
                throw new IllegalReferenceCountException(refCnt, -decrement);
            }

            if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) {
                if (refCnt == decrement) {
                    deallocate();
                    return true;
                }
                return false;
            }
        }
    }

    protected abstract void deallocate();
    ...
}

abstract class PooledByteBuf<T> extends AbstractReferenceCountedByteBuf {
    ...
    protected PoolChunk<T> chunk;
    protected long handle;
    protected T memory;
    ...
    @Override
    protected final void deallocate() {
        if (handle >= 0) {
            //1.清空PooledByteBuf对象的handle、chunk、memory变量值
            final long handle = this.handle;
            this.handle = -1;
            memory = null;
            //2.调用PoolArena的free()方法去释放内存
            //也就是将连续内存的区段添加到缓存 + 标记连续内存的区段为未使用;
            chunk.arena.free(chunk, handle, maxLength, cache);
            //3.调用PooledByteBuf的recycle()方法,将PooledByteBuf对象添加到对象池
            recycle();
        }
    }
}

(2)将连续内存的区段加到缓存

进行内存分配时,第一个步骤就是从缓存里寻找是否有对应大小的连续内存区段,如果有就直接取出来进行分配。

如果释放内存时,将连续内存的区段添加到缓存成功了,那么下次进行内存分配时,对于相同大小的PooledByteBuf,就可以从缓存中直接取出来进行使用了。

如果释放内存时,将连续内存的区段添加到缓存不成功,比如缓存队列已经满了就会不成功,那么就标记该PooledByteBuf对应的连续内存区段为未使用。

在PoolArena的free()方法中,首先会调用PoolThreadCache的add()方法将释放的连续内存区段添加到缓存,然后调用PoolArena的freeChunk()方法标记连续内存的区段为未使用。

代码语言:javascript
复制
abstract class PoolArena<T> implements PoolArenaMetric {
    private final PoolSubpage<T>[] tinySubpagePools;
    private final PoolSubpage<T>[] smallSubpagePools;
    
    private final PoolChunkList<T> qInit;
    private final PoolChunkList<T> q000;
    private final PoolChunkList<T> q025;
    private final PoolChunkList<T> q050;
    private final PoolChunkList<T> q075;
    private final PoolChunkList<T> q100;
    ...
    void free(PoolChunk<T> chunk, long handle, int normCapacity, PoolThreadCache cache) {
        if (chunk.unpooled) {
            int size = chunk.chunkSize();
            destroyChunk(chunk);
            activeBytesHuge.add(-size);
            deallocationsHuge.increment();
        } else {
            //判断要释放的内存大小属于哪一种规格
            SizeClass sizeClass = sizeClass(normCapacity);
            //1.调用PoolThreadCache的add()方法将释放的连续内存区段添加到缓存
            if (cache != null && cache.add(this, chunk, handle, normCapacity, sizeClass)) {
                //cached so not free it.
                return;
            }
            //2.调用PoolArena的freeChunk()方法释放内存,也就是标记连续内存的区段为未使用
            freeChunk(chunk, handle, sizeClass);
        }
    }
    
    private SizeClass sizeClass(int normCapacity) {
        if (!isTinyOrSmall(normCapacity)) {
            return SizeClass.Normal;
        }
        return isTiny(normCapacity) ? SizeClass.Tiny : SizeClass.Small;
    }
    ...
}

final class PoolThreadCache {
    private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;
    private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
    private final MemoryRegionCache<byte[]>[] normalHeapCaches;
    private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
    private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
    private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;
    ...
    //Add PoolChunk and handle to the cache if there is enough room.
    //Returns true if it fit into the cache false otherwise.
    @SuppressWarnings({ "unchecked", "rawtypes" })
    boolean add(PoolArena<?> area, PoolChunk chunk, long handle, int normCapacity, SizeClass sizeClass) {
        MemoryRegionCache<?> cache = cache(area, normCapacity, sizeClass);
        if (cache == null) {
            return false;
        }
        //将PoolChunk的连续内存区段添加到缓存
        return cache.add(chunk, handle);
    }

    private MemoryRegionCache<?> cache(PoolArena<?> area, int normCapacity, SizeClass sizeClass) {
        switch (sizeClass) {
        case Normal:
            return cacheForNormal(area, normCapacity);
        case Small:
            return cacheForSmall(area, normCapacity);
        case Tiny:
            return cacheForTiny(area, normCapacity);
        default:
            throw new Error();
        }
    }
    
    private MemoryRegionCache<?> cacheForTiny(PoolArena<?> area, int normCapacity) {
        int idx = PoolArena.tinyIdx(normCapacity);
        if (area.isDirect()) {
            return cache(tinySubPageDirectCaches, idx);
        }
        return cache(tinySubPageHeapCaches, idx);
    }

    private MemoryRegionCache<?> cacheForSmall(PoolArena<?> area, int normCapacity) {
        int idx = PoolArena.smallIdx(normCapacity);
        if (area.isDirect()) {
            return cache(smallSubPageDirectCaches, idx);
        }
        return cache(smallSubPageHeapCaches, idx);
    }

    private MemoryRegionCache<?> cacheForNormal(PoolArena<?> area, int normCapacity) {
        if (area.isDirect()) {
            int idx = log2(normCapacity >> numShiftsNormalDirect);
            return cache(normalDirectCaches, idx);
        }
        int idx = log2(normCapacity >> numShiftsNormalHeap);
        return cache(normalHeapCaches, idx);
    }

    private static <T> MemoryRegionCache<T> cache(MemoryRegionCache<T>[] cache, int idx) {
        if (cache == null || idx > cache.length - 1) {
            return null;
        }
        return cache[idx];
    }
    ...
    private abstract static class MemoryRegionCache<T> {
        private final Queue<Entry<T>> queue;
        ...
        //Add to cache if not already full.
        //将PoolChunk的连续内存区段添加到缓存
        @SuppressWarnings("unchecked")
        public final boolean add(PoolChunk<T> chunk, long handle) {
            Entry<T> entry = newEntry(chunk, handle);
            boolean queued = queue.offer(entry);
            if (!queued) {
                //If it was not possible to cache the chunk, immediately recycle the entry
                entry.recycle();
            }
            return queued;
        }
        ...
    }
}

(3)标记连续内存的区段为未使用

标记方式会根据Page级别和SubPage级别进行标记,其中Page级别是根据二叉树来进行标记,SubPage级别是通过位图进行标记。

代码语言:javascript
复制
abstract class PoolArena<T> implements PoolArenaMetric {
    ...
    void freeChunk(PoolChunk<T> chunk, long handle, SizeClass sizeClass) {
        final boolean destroyChunk;
        synchronized (this) {
            switch (sizeClass) {
            case Normal:
                ++deallocationsNormal;
                break;
            case Small:
                ++deallocationsSmall;
                break;
            case Tiny:
                ++deallocationsTiny;
                break;
            default:
                throw new Error();
            }
            //调用PoolChunk的parent也就是PoolChunkList的free()方法释放PoolChunk
            destroyChunk = !chunk.parent.free(chunk, handle);
        }
        if (destroyChunk) {
            //destroyChunk not need to be called while holding the synchronized lock.
            destroyChunk(chunk);
        }
    }
    ...
}

final class PoolChunkList<T> implements PoolChunkListMetric {
    private PoolChunk<T> head;
    private PoolChunkList<T> prevList;
    ...
    boolean free(PoolChunk<T> chunk, long handle) {
        //标记PoolChunk中连续内存区段为未使用
        chunk.free(handle);
        //如果要释放的PoolChunk的使用率小于当前PoolChunkList的最小使用率
        if (chunk.usage() < minUsage) {
            //从当前PoolChunkList中移除PoolChunk
            remove(chunk);
            //将PoolChunk添加到当前PoolChunkList的下一个PoolChunkList中
            return move0(chunk);
        }
        return true;
    }
    
    private void remove(PoolChunk<T> cur) {
        if (cur == head) {
            head = cur.next;
            if (head != null) {
                head.prev = null;
            }
        } else {
            PoolChunk<T> next = cur.next;
            cur.prev.next = next;
            if (next != null) {
                next.prev = cur.prev;
            }
        }
    }
    
    //Moves the PoolChunk down the PoolChunkList linked-list so it will end up in the right PoolChunkList 
    //that has the correct minUsage / maxUsage in respect to PoolChunk#usage().
    private boolean move0(PoolChunk<T> chunk) {
        if (prevList == null) {
            //There is no previous PoolChunkList so return false which result in having the PoolChunk destroyed and
            //all memory associated with the PoolChunk will be released.
            assert chunk.usage() == 0;
            return false;
        }
        return prevList.move(chunk);
    }
    ...
}

final class PoolChunk<T> implements PoolChunkMetric {
    final PoolArena<T> arena;
    private final PoolSubpage<T>[] subpages;
    ...
    //Free a subpage or a run of pages 
    //When a subpage is freed from PoolSubpage, it might be added back to subpage pool of the owning PoolArena
    //If the subpage pool in PoolArena has at least one other PoolSubpage of given elemSize, 
    //we can completely free the owning Page so it is available for subsequent allocations
    //@param handle handle to free
    void free(long handle) {
        int memoryMapIdx = memoryMapIdx(handle);
        int bitmapIdx = bitmapIdx(handle);

        if (bitmapIdx != 0) { // free a subpage
            PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)];
            assert subpage != null && subpage.doNotDestroy;

            //Obtain the head of the PoolSubPage pool that is owned by the PoolArena and synchronize on it.
            //This is need as we may add it back and so alter the linked-list structure.
            PoolSubpage<T> head = arena.findSubpagePoolHead(subpage.elemSize);
            synchronized (head) {
                //2.SubPage级别通过位图进行标记
                if (subpage.free(head, bitmapIdx & 0x3FFFFFFF)) {
                    return;
                }
            }
        }
        //1.Page级别根据二叉树来进行标记
        freeBytes += runLength(memoryMapIdx);
        setValue(memoryMapIdx, depth(memoryMapIdx));
        updateParentsFree(memoryMapIdx);
    }
    ...
}

(4)将ByteBuf对象添加到对象池

一开始时,对象池是没有PooledByteBuf对象的,当PooledByteBuf对象被释放时不会被立即销毁,而是会加入到对象池里。

这样当Netty每次去拿一个PooledByteBuf对象时,就可以先从对象池里获取,取出对象之后就可以进行内存分配以及初始化了。

考虑到PooledByteBuf对象会经常被申请和释放,如果QPS非常高,可能会产生很多PooledByteBuf对象,而且频繁创建和释放PooledByteBuf对象也会比较耗费资源和降低性能。

所以Netty便使用了对象池来减少GC:当申请PooledByteBuf对象时,就可以尽可能从对象池里去取。当释放PooledByteBuf对象时,则可以将对象添加到对象池,从而实现对象复用。

代码语言:javascript
复制
abstract class PooledByteBuf<T> extends AbstractReferenceCountedByteBuf {
    private final Recycler.Handle<PooledByteBuf<T>> recyclerHandle;
    ...
    private void recycle() {
        recyclerHandle.recycle(this);
    }
    ...
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档