Netty源碼分析第5章(ByteBuf)---->第6節: 命中緩存的分配

 

Netty源碼分析第6章: ByteBufhtml

 

第六節: 命中緩存的分配數組

 

上一小節簡單分析了directArena內存分配大概流程, 知道其先命中緩存, 若是命中不到, 則區分配一款連續內存, 這一小節帶你們剖析命中緩存的相關邏輯緩存

分析先關邏輯以前, 首先介紹緩存對象的數據結構數據結構

回顧上一小節的內容, 咱們講到PoolThreadCache中維護了三個緩存數組(其實是六個, 這裏僅僅以Direct爲例, heap類型的邏輯是同樣的): tinySubPageDirectCaches, smallSubPageDirectCaches, 和normalDirectCaches分別表明tiny類型, small類型和normal類型的緩存數組源碼分析

這三個數組保存在PoolThreadCache的成員變量中:this

private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches; private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches; private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;

其中是在構造方法中進行了初始化:spa

tinySubPageDirectCaches = createSubPageCaches( tinyCacheSize, PoolArena.numTinySubpagePools, SizeClass.Tiny); smallSubPageDirectCaches = createSubPageCaches( smallCacheSize, directArena.numSmallSubpagePools, SizeClass.Small); normalDirectCaches = createNormalCaches( normalCacheSize, maxCachedBufferCapacity, directArena);

咱們以tiny類型爲例跟到createSubPageCaches方法中:指針

private static <T> MemoryRegionCache<T>[] createSubPageCaches( int cacheSize, int numCaches, SizeClass sizeClass) { if (cacheSize > 0) { @SuppressWarnings("unchecked") MemoryRegionCache<T>[] cache = new MemoryRegionCache[numCaches]; for (int i = 0; i < cache.length; i++) { cache[i] = new SubPageMemoryRegionCache<T>(cacheSize, sizeClass); } return cache; } else { return null; } }

這裏上面的小節已經分析過, 這裏建立了一個緩存數組, 這個緩存數組的長度,也就是numCaches, 在不一樣的類型, 這個長度不同, tiny類型長度是32, small類型長度爲4, normal類型長度爲3netty

咱們知道, 緩存數組中每一個節點表明一個緩存對象, 裏面維護了一個隊列, 隊列大小由PooledByteBufAllocator類中的tinyCacheSize, smallCacheSize, normalCacheSize屬性決定的, 這裏以前小節已經剖析過code

其中每一個緩存對象, 隊列中緩存的ByteBuf大小是固定的, netty將每種緩衝區類型分紅了不一樣長度規格, 而每一個緩存中的隊列緩存的ByteBuf的長度, 都是同一個規格的長度, 而緩衝區數組的長度, 就是規格的數量

 

好比, 在tiny類型中, netty將其長度分紅32個規格, 每一個規格都是16的整數倍, 也就是包含0B, 16B, 32B, 48B, 64B, 80B, 96B......496B總共32種規格, 而在其緩存數組tinySubPageDirectCaches中, 這每一種規格表明數組中的一個緩存對象緩存的ByteBuf的大小, 咱們以tinySubPageDirectCaches[1]爲例(這裏下標選擇1是由於下標爲0表明的規格是0B, 其實就表明一個空的緩存, 這裏不進行舉例), 在tinySubPageDirectCaches[1]的緩存對象中所緩存的ByteBuf的緩衝區長度是16B, 在tinySubPageDirectCaches[2]中緩存的ByteBuf長度都爲32B, 以此類推, tinySubPageDirectCaches[31]中緩存的ByteBuf長度爲496B

 

有關類型規則的分配以下:

tiny:總共32個規格, 均是16的整數倍, 0B, 16B, 32B, 48B, 64B, 80B, 96B......496B

small:4種規格, 512b, 1k, 2k, 4k

nomal:3種規格, 8k, 16k, 32k

這樣, PoolThreadCache中緩存數組的數據結構爲

5-6-1

大概瞭解緩存數組的數據結構, 咱們再繼續剖析在緩衝中分配內存的邏輯

回到PoolArena的allocate方法中:

private void allocate(PoolThreadCache cache, PooledByteBuf<T> buf, final int reqCapacity) { //規格化
    final int normCapacity = normalizeCapacity(reqCapacity); if (isTinyOrSmall(normCapacity)) { int tableIdx; PoolSubpage<T>[] table; //判斷是否是tinty
        boolean tiny = isTiny(normCapacity); if (tiny) { // < 512 //緩存分配
            if (cache.allocateTiny(this, buf, reqCapacity, normCapacity)) { return; } //經過tinyIdx拿到tableIdx
            tableIdx = tinyIdx(normCapacity); //subpage的數組
            table = tinySubpagePools; } else { if (cache.allocateSmall(this, buf, reqCapacity, normCapacity)) { return; } tableIdx = smallIdx(normCapacity); table = smallSubpagePools; } //拿到對應的節點
        final PoolSubpage<T> head = table[tableIdx]; synchronized (head) { final PoolSubpage<T> s = head.next; //默認狀況下, 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; } if (normCapacity <= chunkSize) { //首先在緩存上進行內存分配
        if (cache.allocateNormal(this, buf, reqCapacity, normCapacity)) { //分配成功, 返回
            return; } //分配不成功, 作實際的內存分配
 allocateNormal(buf, reqCapacity, normCapacity); } else { //大於這個值, 就不在緩存上分配
 allocateHuge(buf, reqCapacity); } }

首先經過normalizeCapacity方法進行內存規格化

咱們跟到normalizeCapacity方法中:

int normalizeCapacity(int reqCapacity) { if (reqCapacity < 0) { throw new IllegalArgumentException("capacity: " + reqCapacity + " (expected: 0+)"); } if (reqCapacity >= chunkSize) { return reqCapacity; } //若是>tiny
    if (!isTiny(reqCapacity)) { // >= 512 //找一個2的冪次方的數值, 確保數值大於等於reqCapacity
        int normalizedCapacity = reqCapacity; normalizedCapacity --; normalizedCapacity |= normalizedCapacity >>>  1; normalizedCapacity |= normalizedCapacity >>>  2; normalizedCapacity |= normalizedCapacity >>>  4; normalizedCapacity |= normalizedCapacity >>>  8; normalizedCapacity |= normalizedCapacity >>> 16; normalizedCapacity ++; if (normalizedCapacity < 0) { normalizedCapacity >>>= 1; } return normalizedCapacity; } //若是是16的倍數
    if ((reqCapacity & 15) == 0) { return reqCapacity; } //不是16的倍數, 變成最大小於當前值的值+16
    return (reqCapacity & ~15) + 16; }

 if (!isTiny(reqCapacity)) 表明若是大於tiny類型的大小, 也就是512, 則會找一個2的冪次方的數值, 確保這個數值大於等於reqCapacity

若是是tiny, 則繼續往下

 if ((reqCapacity & 15) == 0) 這裏判斷若是是16的倍數, 則直接返回

若是不是16的倍數, 則返回 (reqCapacity & ~15) + 16 , 也就是變成最小大於當前值的16的倍數值

從上面規格化邏輯看出, 這裏將緩存大小規格化成固定大小, 確保每一個緩存對象緩存的ByteBuf容量統一

 

回到allocate方法中:

 if(isTinyOrSmall(normCapacity)) 這裏是根據規格化後的大小判斷是否tiny或者small類型, 咱們跟到方法中:

boolean isTinyOrSmall(int normCapacity) { return (normCapacity & subpageOverflowMask) == 0; }

這裏是判斷若是normCapacity小於一個page的大小, 也就是8k表明其實tiny或者small

 

繼續看allocate方法:

若是當前大小是tiny或者small, 則isTiny(normCapacity)判斷是不是tiny類型, 跟進去:

static boolean isTiny(int normCapacity) { return (normCapacity & 0xFFFFFE00) == 0; }

這裏是判斷若是小於512, 則認爲是tiny

再繼續看allocate方法:

若是是tiny, 則經過cache.allocateTiny(this, buf, reqCapacity, normCapacity)在緩存上進行分配

咱們就以tiny類型爲例, 分析在緩存上分配ByteBuf的流程

allocateTiny是緩存分配的入口

咱們跟進去, 進入到了PoolThreadCache的allocateTiny方法中:

boolean allocateTiny(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int normCapacity) { return allocate(cacheForTiny(area, normCapacity), buf, reqCapacity); }

這裏有個方法cacheForTiny(area, normCapacity), 這個方法的做用是根據normCapacity找到tiny類型緩存數組中的一個緩存對象

咱們跟進cacheForTiny:

private MemoryRegionCache<?> cacheForTiny(PoolArena<?> area, int normCapacity) { int idx = PoolArena.tinyIdx(normCapacity); if (area.isDirect()) { return cache(tinySubPageDirectCaches, idx); } return cache(tinySubPageHeapCaches, idx); }

PoolArena.tinyIdx(normCapacity)是找到tiny類型緩存數組的下標

繼續跟tinyIdx:

static int tinyIdx(int normCapacity) { return normCapacity >>> 4; }

這裏直接將normCapacity除以16, 經過前面的內容咱們知道, tiny類型緩存數組中每一個元素規格化的數據都是16的倍數, 因此經過這種方式能夠找到其下標, 參考圖5-2, 若是是16B會拿到下標爲1的元素, 若是是32B則會拿到下標爲2的元素

回到acheForTiny方法中:

 if (area.isDirect()) 這裏判斷是不是分配堆外內存, 由於咱們是按照堆外內存進行舉例, 因此這裏爲true

再繼續跟到cache(tinySubPageDirectCaches, idx)方法中:

private static <T> MemoryRegionCache<T> cache(MemoryRegionCache<T>[] cache, int idx) { if (cache == null || idx > cache.length - 1) { return null; } return cache[idx]; }

這裏咱們看到直接經過下標的方式拿到了緩存數組中的對象

回到PoolThreadCache的allocateTiny方法中:

boolean allocateTiny(PoolArena<?> area, PooledByteBuf<?> buf, int reqCapacity, int normCapacity) { return allocate(cacheForTiny(area, normCapacity), buf, reqCapacity); }

拿到了緩存對象以後, 咱們跟到allocate(cacheForTiny(area, normCapacity), buf, reqCapacity)方法中:

private boolean allocate(MemoryRegionCache<?> cache, PooledByteBuf buf, int reqCapacity) { if (cache == null) { return false; } boolean allocated = cache.allocate(buf, reqCapacity); if (++ allocations >= freeSweepAllocationThreshold) { allocations = 0; trim(); } return allocated; }

這裏經過cache.allocate(buf, reqCapacity)進行繼續進行分配

再繼續往裏跟, 跟到內部類MemoryRegionCache的allocate(PooledByteBuf<T> buf, int reqCapacity)方法中:

public final boolean allocate(PooledByteBuf<T> buf, int reqCapacity) { Entry<T> entry = queue.poll(); if (entry == null) { return false; } initBuf(entry.chunk, entry.handle, buf, reqCapacity); entry.recycle(); ++ allocations; return true; }

這裏首先經過queue.poll()這種方式彈出一個entry, 咱們以前的小節分析過, MemoryRegionCache維護着一個隊列, 而隊列中的每個值是一個entry

咱們簡單看下Entry這個類:

static final class Entry<T> { final Handle<Entry<?>> recyclerHandle; PoolChunk<T> chunk; long handle = -1; //代碼省略
}

這裏重點關注chunk和handle的這兩個屬性, chunk表明一塊連續的內存, 咱們以前簡單介紹過, netty是經過chunk爲單位進行內存分配的, 咱們以後會對chunk進行剖析

handle至關於一個指針, 能夠惟必定位到chunk裏面的一塊連續的內存, 以後也會詳細分析

這樣, 經過chunk和handle就能夠定位ByteBuf中指定一塊連續內存, 有關ByteBuf相關的讀寫, 都會在這塊內存中進行

咱們回到MemoryRegionCache的allocate(PooledByteBuf<T> buf, int reqCapacity)方法:

public final boolean allocate(PooledByteBuf<T> buf, int reqCapacity) { Entry<T> entry = queue.poll(); if (entry == null) { return false; } initBuf(entry.chunk, entry.handle, buf, reqCapacity); entry.recycle(); ++ allocations; return true; }

彈出entry以後, 經過initBuf(entry.chunk, entry.handle, buf, reqCapacity)這種方式給ByteBuf初始化, 這裏參數傳入咱們剛纔分析過的當前Entry的chunk和hanle

由於咱們分析的tiny類型的緩存對象是SubPageMemoryRegionCache類型,因此咱們繼續跟到SubPageMemoryRegionCache類的initBuf(entry.chunk, entry.handle, buf, reqCapacity)方法中:

protected void initBuf( PoolChunk<T> chunk, long handle, PooledByteBuf<T> buf, int reqCapacity) { chunk.initBufWithSubpage(buf, handle, reqCapacity); }

這裏的chunk調用了initBufWithSubpage(buf, handle, reqCapacity)方法, 其實就是PoolChunk類中的方法

咱們繼續跟initBufWithSubpage:

void initBufWithSubpage(PooledByteBuf<T> buf, long handle, int reqCapacity) { initBufWithSubpage(buf, handle, bitmapIdx(handle), reqCapacity); }

這裏有關bitmapIdx(handle)相關的邏輯, 會在後續的章節進行剖析, 這裏繼續往裏跟:

private void initBufWithSubpage(PooledByteBuf<T> buf, 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, handle, runOffset(memoryMapIdx) + (bitmapIdx & 0x3FFFFFFF) * subpage.elemSize, reqCapacity, subpage.elemSize, arena.parent.threadCache()); }

這裏咱們先關注init方法, 由於咱們是以PooledUnsafeDirectByteBuf爲例, 因此這裏走的是PooledUnsafeDirectByteBuf的init方法

跟進init方法:

void init(PoolChunk<ByteBuffer> chunk, long handle, int offset, int length, int maxLength, PoolThreadCache cache) { super.init(chunk, handle, offset, length, maxLength, cache); initMemoryAddress(); }

首先調用了父類的init方法, 再跟進去:

void init(PoolChunk<T> chunk, long handle, int offset, int length, int maxLength, PoolThreadCache cache) { //初始化
    assert handle >= 0; assert chunk != null; //在哪一塊內存上進行分配的
    this.chunk = chunk; //這一塊內存上的哪一塊連續內存
    this.handle = handle; memory = chunk.memory; this.offset = offset; this.length = length; this.maxLength = maxLength; tmpNioBuf = null; this.cache = cache; }

這裏將PooledUnsafeDirectByteBuf的各個屬性進行了初始化

 this.chunk = chunk 這裏初始化了chunk, 表明當前的ByteBuf是在哪一塊內存中分配的

 this.handle = handle 這裏初始化了handle, 表明當前的ByteBuf是這塊內存的哪一個連續內存

有關offset和length, 咱們會在以後的小節進行分析, 在這裏咱們只須要知道, 經過緩存分配ByteBuf, 咱們只須要經過一個chunk和handle, 就能夠肯定一塊內存

以上就是經過緩存分配ByteBuf對象的過程

咱們回到MemoryRegionCache的allocate(PooledByteBuf<T> buf, int reqCapacity)方法:

public final boolean allocate(PooledByteBuf<T> buf, int reqCapacity) { Entry<T> entry = queue.poll(); if (entry == null) { return false; } initBuf(entry.chunk, entry.handle, buf, reqCapacity); entry.recycle(); ++ allocations; return true; }

分析完了initBuf方法, 再繼續往下看

entry.recycle()這步是將entry對象進行回收, 由於entry對象彈出以後沒有再被引用, 可能gc會將entry對象回收, netty爲了將對象進行循環利用, 就將其放在對象回收站進行回收

咱們跟進recycle方法:

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

chunk = null和handle = -1表示當前Entry不指向任何一塊內存

 recyclerHandle.recycle(this) 將當前entry回收, 有關對象回收站, 咱們會在後面的章節詳細剖析

以上就是命中緩存的流程, 由於這裏咱們是假設緩中有值的狀況下進行分配的, 若是第一次分配, 緩存中是沒有值的, 那麼在緩存中沒有值的狀況下, netty是如何進行分配的呢?咱們再以後的小節會進行剖析

 

上一節: directArena分配緩衝區概述

下一節: page級別的內存分配

相關文章
相關標籤/搜索