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放回Recycler對象池進行復用。

申請新內存分配

當無法命中緩存時,就需要去系統申請新的內存,PoolArena會按照不同內存規格範圍,根據chunk、page、subpage幾個級別按照一定算法去分配。


ByteBuf的釋放與回收

  1. 將連續的內存區段加入到緩存。這裏連續的內存可理解爲上面提到的entry對象,由它的handle與chunk屬性確定位置與大小,結合所屬的內存規格加入到對應的緩存queue中。
  2. 如果緩存隊列已經滿了,則將這塊內存標記爲未使用。
  3. 將使用過的ByteBuf對象加入到Recycler對象池。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章