Netty之内存管理

目录

Netty内存规格介绍

内存区间划分

内存分配单位

内存分配

命中缓存分配

申请新内存分配

ByteBuf的释放与回收


Netty内存规格介绍


内存区间划分

  • tiny:0~512B
  • small:512B~8K
  • normal:8K~16M
  • huge:16M以上

内存分配单位

  • Chunk:Netty中所有内存都是以Chunk为单位分配的,一个Chunk有16M,例如当前需要1M内存,那么就需要向系统申请一个Chunk单位的内存,然后再从这个Chunk中进一步划分。
  • Page:Chunk的划分单位为Page,一个Page有8K,那么一个Chunk就可以划分出2048个Page。
  • SubPage:有时候我们需要的内存远达不到一个Page的大小,那么Netty根据实际需要对Page进一步划分成SubPage。

内存分配

内存分配的逻辑在PoolArena的allocate方法中。

    private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) {
        final int normCapacity = normalizeCapacity(reqCapacity);
        // < 8K
        if (isTinyOrSmall(normCapacity)) { // capacity < pageSize
            int tableIdx;
            PoolSubpage<T>[] table;
            boolean tiny = isTiny(normCapacity);
            if (tiny) { // < 512
                if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) {
                    // was able to allocate out of the cache so move on
                    return;
                }
                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;
                }
                tableIdx = smallIdx(normCapacity);
                table = smallSubpagePools;
            }

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

            /**
             * Synchronize on the head. This is needed as {@link PoolChunk#allocateSubpage(int)} and
             * {@link PoolChunk#free(long)} may modify the doubly linked list as well.
             */
            synchronized (head) {
                final PoolSubpage<T> s = head.next;
                if (s != head) {
                    assert s.doNotDestroy && s.elemSize == normCapacity;
                    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;
        }
        // <= 16M
        if (normCapacity <= chunkSize) {
            if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) {
                // was able to allocate out of the cache so move on
                return;
            }
            allocateNormal(buf, reqCapacity, normCapacity);
        } else {
            // Huge allocations are never served via the cache so just call allocateHuge
            allocateHuge(buf, reqCapacity);
        }
    }

代码中的逻辑很清晰,根据传入的reqCapacity大小选择对应的内存规格。

tiny or small:< 8KB

tiny:< 512B

small:512B ~ 8KB

normal:<= 16MB

huge:> 16MB


命中缓存分配

通过上面的代码可以发现,PoolArena在分配过程中会先从缓存中寻找,看是否有可用的ByteBuf,如果没有再从新申请。这个缓存的数据结构就是MemoryRegionCache。

MemoryRegionCache数据结构

    private abstract static class MemoryRegionCache<T> {
        private final int size;
        private final Queue<Entry<T>> queue;
        private final SizeClass sizeClass;
        private int allocations;

        MemoryRegionCache(int size, SizeClass sizeClass) {
            this.size = MathUtil.safeFindNextPositivePowerOfTwo(size);
            queue = PlatformDependent.newFixedMpscQueue(this.size);
            this.sizeClass = sizeClass;
        }
        ......
        static final class Entry<T> {
            final Handle<Entry<?>> recyclerHandle;
            PoolChunk<T> chunk;
            long handle = -1;

            Entry(Handle<Entry<?>> recyclerHandle) {
                this.recyclerHandle = recyclerHandle;
            }

            void recycle() {
                chunk = null;
                handle = -1;
                recyclerHandle.recycle(this);
            }
        }
        ......
    }

queue:由Entry组成的队列,Entry又是于handle和chunk组成,Netty中所有内存都是以chunk为单位进行分配的,handle指向了一段连续的内存,通过chunk与handle就可以确定一段内存的大小和位置。

sizeClass:指的是按大小划分的三种内存规格。tiny(0~512B)、small(512B~8K)、normal(8K~16M)。huge不会被缓存,所以不在这里。

size:在同一个MemoryRegionCache的queue中,所有元素的大小都是一致的。这个size指定的就是这个统一的大小,也可以理解为是在sizeClass基础上进一步划分的结果。

规格 queue数量 元素大小分类
tiny 32 16B、32B、48B......480B、496B(N*16B)
small 4 512K、1K、2K、4K
normal 3 8K、16、32K

可以看到Netty对内存分配的划分是非常细致的,这样才能尽可能的保证内存的连续性,从而提升效率。 


MemoryRegionCache的分类与创建

我们知道每一个线程都会维护一个PoolThreadCache对象,那么对应的MemoryRegionCache也维护在这里。

final class PoolThreadCache {
    ......
    // Hold the caches for the different size classes, which are tiny, small and normal.
    private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;
    private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
    private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
    private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
    private final MemoryRegionCache<byte[]>[] normalHeapCaches;
    private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;
    ......
}

这里heap与direct都分别有tiny、small、normal三种类型的cache数组。

数组初始化的逻辑在PoolThreadCache的构造函数中:

    PoolThreadCache(PoolArena<byte[]> heapArena, PoolArena<ByteBuffer> directArena,
                    int tinyCacheSize, int smallCacheSize, int normalCacheSize,
                    int maxCachedBufferCapacity, int freeSweepAllocationThreshold) {
        ......
        if (directArena != null) {
            tinySubPageDirectCaches = createSubPageCaches(
                    tinyCacheSize, PoolArena.numTinySubpagePools, SizeClass.Tiny);
            smallSubPageDirectCaches = createSubPageCaches(
                    smallCacheSize, directArena.numSmallSubpagePools, SizeClass.Small);

            numShiftsNormalDirect = log2(directArena.pageSize);
            normalDirectCaches = createNormalCaches(
                    normalCacheSize, maxCachedBufferCapacity, directArena);

            directArena.numThreadCaches.getAndIncrement();
        } else {
            // No directArea is configured so just null out all caches
            tinySubPageDirectCaches = null;
            smallSubPageDirectCaches = null;
            normalDirectCaches = null;
            numShiftsNormalDirect = -1;
        }
        ......
    }

命中缓存分配总结:

  1. 找到对应size的MemoryRegionCache。
  2. 从queue中弹出一个entry给ByteBuf初始化。
  3. 将entry放回recycle对象池进行复用。

申请新内存分配

当无法命中缓存时,就需要去系统申请新的内存,PoolArena会按照不同内存规格范围,根据chunk、page、subpage几个级别按照一定算法去分配。


ByteBuf的释放与回收

  1. 将连续的内存区段加入到缓存。这里连续的内存可理解为上面提到的entry对象,由它的handle与chunk属性确定位置与大小,结合所属的内存规格加入到对应的缓存queue中。
  2. 如果缓存队列已经满了,则将这块内存标记为未使用。
  3. 将使用过的ByteBuf对象加入到Recycle对象池。
发布了158 篇原创文章 · 获赞 109 · 访问量 21万+

猜你喜欢

转载自blog.csdn.net/u011212394/article/details/104004144