Netty源碼分析第5章(ByteBuf)---->第9節: ByteBuf回收

 

Netty源碼分析第五章: ByteBufhtml

 

第九節: ByteBuf回收緩存

 

以前的章節咱們提到過, 堆外內存是不受jvm垃圾回收機制控制的, 因此咱們分配一塊堆外內存進行ByteBuf操做時, 使用完畢要對對象進行回收, 這一小節, 就以PooledUnsafeDirectByteBuf爲例講解有關內存分配的相關邏輯jvm

PooledUnsafeDirectByteBuf中內存釋放的入口方法是其父類AbstractReferenceCountedByteBuf中的release方法:ide

 @Override public boolean release() { return release0(1); }

這裏調用了release0, 跟進去:源碼分析

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; } } }

 if (refCnt == decrement) 中判斷當前byteBuf是否沒有被引用了, 若是沒有被引用, 則經過deallocate()方法進行釋放this

由於咱們是以PooledUnsafeDirectByteBuf爲例, 因此這裏會調用其父類PooledByteBuf的deallocate方法:spa

protected final void deallocate() { if (handle >= 0) { final long handle = this.handle; this.handle = -1; memory = null; chunk.arena.free(chunk, handle, maxLength, cache); recycle(); } }

this.handle = -1表示當前的ByteBuf再也不指向任何一塊內存code

memory = null這裏將memory也設置爲nullorm

chunk.arena.free(chunk, handle, maxLength, cache)這一步是將ByteBuf的內存進行釋放htm

recycle()是將對象放入的對象回收站, 循環利用

咱們首先分析free方法:

void free(PoolChunk<T> chunk, long handle, int normCapacity, PoolThreadCache cache) { //是否爲unpooled
    if (chunk.unpooled) { int size = chunk.chunkSize(); destroyChunk(chunk); activeBytesHuge.add(-size); deallocationsHuge.increment(); } else { //那種級別的Size
        SizeClass sizeClass = sizeClass(normCapacity); //加到緩存裏
        if (cache != null && cache.add(this, chunk, handle, normCapacity, sizeClass)) { return; } //將緩存對象標記爲未使用
 freeChunk(chunk, handle, sizeClass); } }

首先判斷是否是unpooled, 咱們這裏是Pooled, 因此會走到else塊中:

sizeClass(normCapacity)計算是哪一種級別的size, 咱們按照tiny級別進行分析

cache.add(this, chunk, handle, normCapacity, sizeClass)是將當前當前ByteBuf進行緩存

咱們以前講過, 再分配ByteBuf時首先在緩存上分配, 而這步, 就是將其緩存的過程, 跟進去:

boolean add(PoolArena<?> area, PoolChunk chunk, long handle, int normCapacity, SizeClass sizeClass) { //拿到MemoryRegionCache節點
    MemoryRegionCache<?> cache = cache(area, normCapacity, sizeClass); if (cache == null) { return false; } //將chunk, 和handle封裝成實體加到queue裏面
    return cache.add(chunk, handle); }

首先根據根據類型拿到相關類型緩存節點, 這裏會根據不一樣的內存規格去找不一樣的對象, 咱們簡單回顧一下, 每一個緩存對象都包含一個queue, queue中每一個節點是entry, 每個entry中包含一個chunk和handle, 能夠指向惟一的連續的內存

咱們跟到cache中:

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(); } }

假設咱們是tiny類型, 這裏就會走到cacheForTiny(area, normCapacity)方法中, 跟進去:

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

這個方法咱們以前剖析過, 就是根據大小找到第幾個緩存中的第幾個緩存, 拿到下標以後, 經過cache去超相對應的緩存對象:  

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

咱們這裏看到, 是直接經過下標拿的緩存對象

回到add方法中:

boolean add(PoolArena<?> area, PoolChunk chunk, long handle, int normCapacity, SizeClass sizeClass) { //拿到MemoryRegionCache節點
    MemoryRegionCache<?> cache = cache(area, normCapacity, sizeClass); if (cache == null) { return false; } //將chunk, 和handle封裝成實體加到queue裏面
    return cache.add(chunk, handle); }

這裏的cache對象調用了一個add方法, 這個方法就是將chunk和handle封裝成一個entry加到queue裏面

咱們跟到add方法中:

public final boolean add(PoolChunk<T> chunk, long handle) { Entry<T> entry = newEntry(chunk, handle); boolean queued = queue.offer(entry); if (!queued) { entry.recycle(); } return queued; }

咱們以前介紹過, 從在緩存中分配的時候從queue彈出一個entry, 會放到一個對象池裏面, 而這裏Entry<T> entry = newEntry(chunk, handle)就是從對象池裏去取一個entry對象, 而後將chunk和handle進行賦值

而後經過queue.offer(entry)加到queue中

 

咱們回到free方法中:

void free(PoolChunk<T> chunk, long handle, int normCapacity, PoolThreadCache cache) { //是否爲unpooled
    if (chunk.unpooled) { int size = chunk.chunkSize(); destroyChunk(chunk); activeBytesHuge.add(-size); deallocationsHuge.increment(); } else { //那種級別的Size
        SizeClass sizeClass = sizeClass(normCapacity); //加到緩存裏
        if (cache != null && cache.add(this, chunk, handle, normCapacity, sizeClass)) { return; } freeChunk(chunk, handle, sizeClass); } }

這裏加到緩存以後, 若是成功, 就會return, 若是不成功, 就會調用freeChunk(chunk, handle, sizeClass)方法, 這個方法的意義是, 將原先給ByteBuf分配的內存區段標記爲未使用

跟進freeChunk簡單分析下:

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(); } destroyChunk = !chunk.parent.free(chunk, handle); } if (destroyChunk) { destroyChunk(chunk); } }

咱們再跟到free方法中:

boolean free(PoolChunk<T> chunk, long handle) { chunk.free(handle); if (chunk.usage() < minUsage) { remove(chunk); return move0(chunk); } return true; }

chunk.free(handle)的意思是經過chunk釋放一段連續的內存

再跟到free方法中:

void free(long handle) { int memoryMapIdx = memoryMapIdx(handle); int bitmapIdx = bitmapIdx(handle); if (bitmapIdx != 0) { PoolSubpage<T> subpage = subpages[subpageIdx(memoryMapIdx)]; assert subpage != null && subpage.doNotDestroy; PoolSubpage<T> head = arena.findSubpagePoolHead(subpage.elemSize); synchronized (head) { if (subpage.free(head, bitmapIdx & 0x3FFFFFFF)) { return; } } } freeBytes += runLength(memoryMapIdx); setValue(memoryMapIdx, depth(memoryMapIdx)); updateParentsFree(memoryMapIdx); }

 if (bitmapIdx != 0)這 裏判斷是當前緩衝區分配的級別是Page仍是Subpage, 若是是Subpage, 則會找到相關的Subpage將其位圖標記爲0

若是不是subpage, 這裏經過分配內存的反向標記, 將該內存標記爲未使用

這段邏輯能夠讀者自行分析, 若是以前分配相關的知識掌握紮實的話, 這裏的邏輯也不是很難

回到PooledByteBuf的deallocate方法中:

protected final void deallocate() { if (handle >= 0) { final long handle = this.handle; this.handle = -1; memory = null; chunk.arena.free(chunk, handle, maxLength, cache); recycle(); } }

最後, 經過recycle()將釋放的ByteBuf放入對象回收站, 有關對象回收站的知識, 會在之後的章節進行剖析

以上就是內存回收的大概邏輯

 

 

上一節: Subpage級別的內存分配

下一節: SocketChannel讀取數據的過程 

相關文章
相關標籤/搜索