当前位置: 首页 > 后端技术 > Java

[Netty]二、ByteBuf简介

时间:2023-04-01 19:55:54 Java

1、ByteBuf简介NIO的ByteBuffer大家都很熟悉了。它其实是一个字节容器,但是用过ByteBuffer的童鞋都知道ByteBuffer使用起来比较复杂,比如write模式完成后。需要手动调用flip()方法切换到阅读模式,然后进行相应的阅读。Netty中的ByteBuf是ByteBuffer的升级版。它为开发者提供了更方便的API,并扩展了一些其他功能。接下来介绍一下ByteBuf。识别,如下图类图,可以看到ByteBuf实现了ReferenceCounted接口。这是因为ByteBuf使用了引用计数。当ByteBuf的引用数为0时,表示该ByteBuf不再可用,需要显式释放。ByteBuf下的AbstractByteBuf实现了ByteBuf的一些常用方法,如isReadable、isWritable、readableBytes、writableBytes等。继续往下看是AbstractReferenceCountedByteBuf,它在AbstractByteBuf的基础上完成了引用计数的相关操作。底层类可以分为三种ByteBuf,分别是PooledByteBuf(池化)、UnpooledByteBuf(非池化)和CompositeByteBuf(复合),其中PooledByteBuf又分为PooledHeapByteBuf(池化堆内存)和PooledDirectByteBuf(池化直接内存),UnpooledByteBuf是一样的。ByteBuf主要提供以下操作方法说明alloc()返回创建ByteBuf的ByteBufAllocator分配器容量capacity()ByteBuf可以存储的最大字节数hasArray()返回truearray()如果ByteBuf是字节数组Byte数组,通常与hasArray()配合使用readerIndex()返回ByteBuf的当前读取索引writerIndex()返回ByteBuf当前的写入索引isReadable()是否有可供读取的字节isWritable()是否有可用的字节数bytesthatcanbereadbywritableBytes()writableBytes()可以写入的字节数getByte(int)返回给定索引处的字节,读取索引保持不变,同时提供了类似getByte的方法。例如getInt()、getLong、getShortsetByte(intindex,intvalue)设置给定索引的字节值,写入索引不变。它还提供了类似于setByte的方法,如setInt、setLong、setShortreadByte()返回当前读取索引readerIndex处的字节,并将readerIndex加1。writeByte()在当前写入索引writerIndex处写入一个字节值,并将writerIndex加1。通过readerIndex()和writerIndex()方法可以看出,ByteBuf有两个索引,一个读一个写,所以写完可以直接读,不需要像ByteBuffer一样调用flip()方法切换读模式,如下图所示:2.引用计数上面提到,ByteBuf实现了ReferenceCounted接口。ReferenceCounted接口主要包括refCnt()增加引用数,retain()增加引用数,release()减少引用数等,如下://returnthenumberofreferencesintrefCnt();//引用数加1ReferenceCountedretain();//引用数加incrementReferenceCountedretain(intincrement);//引用数减1booleanrelease();//引用数减去decrementbooleanrelease(intdecrement);ByteBuf具体的引用计数相关操作都在AbstractReferenceCountedByteBuf中完成,接下来看AbstractReferenceCountedByteBuf的源码,部分源码如下:publicintrefCnt(){returnupdater.refCnt(this);}publicByteBufretain(){returnupdater.retain(this);}publicByteBufretain(intincrement){returnupdater.retain(this,increment);}publicbooleanrelease(){returnhandleRelease(updater.release(this));}publicbooleanrelease(intdecrement){returnhandleRelease(updater.release(this,decrement));}可以看到这些方法都是通过updater更新器内部实现的,那么我们来看看这个updater更新器是什么?privatestaticfinallongREFCNT_FIELD_OFFSET=ReferenceCountUpdater.getUnsafeOffset(AbstractReferenceCountedByteBuf.class,"refCnt");privatestaticfinalAtomicIntegerFieldUpdaterAIF_UPDATER=AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class,"refCnt");privatestaticfinalReferenceCountUpdaterupdater=newReferenceCountUpdater(){@OverrideprotectedAtomicIntegerFieldUpdaterupdater(){返回AIF_UPDATER;}@OverrideprotectedlongunsafeOffset(){returnREFCNTda;源代码}DReferenceCountUpdater类型内部使用通过update()方法得到的AtomicIntegerFieldUpdater来原子更新引用计数字段,这里引用计数字段为refCnt。ReferenceCountUpdate的计数方式与普通计数有些不同。每增加一个引用,refCnt对应的值就会增加2,减少一个引用,refCnt对应的值就会减2。如果最后一个引用被释放,refCnt对应的值就会变成1.所以如果refCnt的值为1,则表示实际引用数为0,否则实际引用数=refCnt/23.PooledByteBuf和UnpooledByteBufPooledByteBuf(pooled)和UnpooledByteBuf(non-pooled)是两种类型字节缓冲区。顾名思义,一种是从内存池中分配的ByteBuf,使用后归还池,另一种是直接从内存中分配的ByteBuf,使用完后会被回收。UnpooledByteBuf分为UnpooledHeapByteBuf(非池化堆内存)和UnpooledDirectByteBuf(非池化直接内存)。UnpooledHeapByteBuf和UnpooledDirectByteBuf的实现比较简单。UnpooledHeapByteBuf内部是使用字节数组实现的,而UnpooledDirectByteBuf内部是通过NIO的DirectByteBuffer实现的。3.1池化机制简介每个线程绑定一个PoolThreadCache,其中包含一个heapArena(堆内存)和一个directArena(直接内存),都是PoolArena类型。每个线程通过PooledByteBufAllocator分配内存时,都会在其绑定的PoolThreadCache中进行分配。PoolArena的结构图如下。下面是如何分配一个池化的PooledByteBuf。源代码中有更多详细信息。这里只是简单介绍一下PooledByteBufAllocator中的newHeapBuffer和newDirectBuffer方法。这里以newHeapBuffer为例,如下@OverrideprotectedByteBufnewHeapBuffer(intinitialCapacity,intmaxCapacity){//获取当前线程的PoolThreadCachePoolThreadCachecache=threadCache.get();//获取PoolThreadCache的heapArenaPoolArenaheapArena=cache.heapArena;最终的ByteBuf缓冲区;if(heapArena!=null){//使用heapArena分配ByteBufbuf=heapArena.allocate(cache,initialCapacity,maxCapacity);}else{//如果没有heapArena,分配一个非池化的ByteBufbuf=PlatformDependent.hasUnsafe()?newUnpooledUnsafeHeapByteBuf(this,initialCapacity,maxCapacity):newUnpooledHeapByteBuf(this,initialCapacity,maxCapacity);}returntoLeakAwareBuffer(buf);}调用heapArena的allocate方法进行分配,先根据需要的内存大小计算出sizeIdx,然后判断sizeIdx,分为small、normal、huge三种情况。如下:privatevoidallocate(PoolThreadCachecache,PooledByteBufbuf,finalintreqCapacity){//根据要分配的内存大小计算sizeIdxfinalintsizeIdx=size2SizeIdx(reqCapacity);if(sizeIdx<=smallMaxSizeIdx){//如果sizeIdx<=smallMaxSizeIdx,说明需要的内存小,PoolSubpage主要用于分配tcacheAllocateSmall(cache,buf,reqCapacity,sizeIdx);}elseif(sizeIdx0?normalizeSize(reqCapacity):reqCapacity;allocateHuge(buf,normCapacity);}}4.总结ByteBuf是netty的基本组件。与ByteBuffer相比,ByteBuf不仅使用起来更方便,还提供了更全面的API,并且使用Pooling技术来优化内存分配效率。

最新推荐
猜你喜欢