好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

网络分布式字节缓冲存储器使用的回收逻辑分析

这篇文章主要介绍了Netty分布式ByteBuf使用的回收逻辑剖析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

Portal: ByteBuf使用子页级别的内存分配。

ByteBuf回收

正如我们在上一章中提到的,堆外内存不受jvm垃圾收集机制的控制,所以当我们为ByteBuf操作分配一块堆外内存时,我们需要在使用后回收该对象。在本节中,我们将以PooledUnsafeDirectByteBuf为例来解释内存分配的相关逻辑。

PooledUnsafeDirectByteBuf中内存释放的入口方法是其父类abstractreferencecountedbytebuf中的释放方法:

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

这里调用了release0, 跟进去private boolean release0(int decrement) { for (;;) { int refCnt = this.refCnt; if (refCnt lt; decrement) { throw new IllegalReferenceCountException(refCnt, -decrement); } if (refCntUpdater测试数据pareAndSet(this, refCnt, refCnt - decrement)) { if (refCnt == decrement) { deallocate(); return true; } return false; } }}

如果(refCnt == decrement),则判断当前byteBuf是否未被引用,如果没有,则通过deallocate()方法释放。

因为我们以PooledUnsafeDirectByteBuf为例,这里会调用其父类PooledByteBuf的deallocate方法:

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

This.handle = -1表示当前ByteBuf不再指向任何内存块。

Memory = null这里,Memory也被设置为null。

Chunk.arena.free (chunk,handle,maxlength,cache)这一步是释放ByteBuf的内存。

Recycle()是将对象放入对象回收站并回收。

我们首先分析free方法void free(PoolChunklt;Tgt; 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 amp;amp; cache.add(this, chunk, handle, normCapacity, sizeClass)) { return; } //将缓存对象标记为未使用 freeChunk(chunk, handle, sizeClass); }}

首先,判断是否未上盘。我们在这里汇集,因此我们将转到else块:

SICLASS(定额容量)计算的是什么样的尺寸?我们按照微小的层面来分析。

Cache.add (this,chunk,handle,norm capacity,sizeclass)是缓存当前的ByteBuf。

我们之前说过,在重新分发ByteBuf的时候,首先是在缓存上分发,这一步就是缓存它的过程,接下来是:

boolean add(PoolArenalt;?gt; area, PoolChunk chunk, long handle, int normCapacity, SizeClass sizeClass) { //拿到MemoryRegionCache节点 MemoryRegionCachelt;?gt; cache = cache(area, normCapacity, sizeClass); if (cache == null) { return false; } //将chunk, 和handle封装成实体加到queue里面 return cache.add(chunk, handle);}

首先,根据类型,获取相关类型的缓存节点。在这里,我们将根据不同的内存规格来寻找不同的对象。我们简单回顾一下,每个缓存对象都包含一个队列,而队列中的每个节点,queue都是一个条目,每个条目都包含一个chunk和handle,可以指向一个唯一的、连续的内存。

我们跟到cache中private MemoryRegionCachelt;?gt; cache(PoolArenalt;?gt; 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类型,这里我们将转到cachefortini(area,normCapacity)方法并遵循:

private MemoryRegionCachelt;?gt; cacheForTiny(PoolArenalt;?gt; area, int normCapacity) { int idx = PoolArena.tinyIdx(normCapacity); if (area.isDirect()) { return cache(tinySubPageDirectCaches, idx); } return cache(tinySubPageHeapCaches, idx);}

我们之前分析过这种方法,就是根据大小找出哪个缓存在哪个缓存中,得到下标后,通过cache超越对应的cache对象:

private static lt;Tgt; MemoryRegionCachelt;Tgt; cache(MemoryRegionCachelt;Tgt;[] cache, int idx) { if (cache == null || idx gt; cache.length - 1) { return null; } return cache[idx];}

在这里,我们看到它是一个直接由下标获取的缓存对象。

回到add方法中boolean add(PoolArenalt;?gt; area, PoolChunk chunk, long handle, int normCapacity, SizeClass sizeClass) { //拿到MemoryRegionCache节点 MemoryRegionCachelt;?gt; cache = cache(area, normCapacity, sizeClass); if (cache == null) { return false; } //将chunk, 和handle封装成实体加到queue里面 return cache.add(chunk, handle);}

这里的cache对象调用add方法,该方法将块和句柄封装到一个条目中,并将其添加到队列中。

让我们遵循添加方法:

public final boolean add(PoolChunklt;Tgt; chunk, long handle) { Entrylt;Tgt; entry = newEntry(chunk, handle); boolean queued = queue.offer(entry); if (!queued) { entry.recycle(); } return queued;}

正如我们之前提到的,当一个条目在缓存中分配时从队列中弹出,它将被放入对象池,这里是EntryltTgtEntry = newEntry(chunk,handle)是从对象池中取出一个entry对象,然后分配chunk和handle。

然后通过queue.offer(entry)添加到队列中。

我们回到free方法中void free(PoolChunklt;Tgt; 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 amp;amp; cache.add(this, chunk, handle, normCapacity, sizeClass)) { return; } freeChunk(chunk, handle, sizeClass); }}

在这里加入缓存后,如果成功,就返回;如果失败,它将调用free chunk (chunk,handle,sizeclass)方法。这个方法的意义是将原来分配给ByteBuf的内存段标记为未使用。

跟进freeChunk,做一个简单的分析:

void freeChunk(PoolChunklt;Tgt; 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); }}

让我们遵循免费的方法:

boolean free(PoolChunklt;Tgt; chunk, long handle) { chunk.free(handle); if (chunk.usage() lt; minUsage) { remove(chunk); return move0(chunk); } return true;}

Chunk.free(handle)的意思是通过Chunk释放一个连续的内存。

然后按照免费的方法:

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

if(bitmapIdx!= 0)这里判断当前的缓冲区分配级别是页面还是子页面。如果是子页,会找到相关的子页,其位图会标记为0。

如果不是子页,通过分配内存的反向标记,将内存标记为未使用。

这个逻辑可以由读者自己分析,如果之前的发行相关知识扎实,这里的逻辑也不是很难。

回到PooledByteBuf的deallocate方法:

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

最后通过recycle()将释放的ByteBuf放入对象回收站。关于对象回收站的知识将在后面的章节中分析。

以上是内存回收的一般逻辑。更多关于ByteBuf分发的Netty的回收信息,请关注搜源网其他相关文章!

查看更多关于网络分布式字节缓冲存储器使用的回收逻辑分析的详细内容...

  阅读:29次