Netty - ByteBuf

83 阅读2分钟

ByteBuf

ByteBuf是netty对jdk自带ByteBuffer的增强,用于channel中数据交换的载体。 对比jdk中的ByteBuffer有以下优势:

  • 读写指针分离:不用再flip切换模式,简单易用
  • 自动扩容:再也不用担心容量不够用
  • 池化管理:高并发下,防止频繁创建销毁分配,该功能可以关闭

创建

ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(capacity);
ByteBuf buf = ByteBufAllocator.DEFAULT.heapBuffer(capacity);//分配堆内存
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(capacity);//分配直接内存,默认

//在Handler中通常使用以下方法创建ByteBuf
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    ByteBuf buf = ctx.alloc().buffer(capacity);
}

扩容规则

数据大小未超过512之前,每次扩容为不小于所需容量的16的整数倍(16,32,48....)
数据大小超过512之后,每次扩容为不小于所需容量的2^n(512,1024,2048....)
扩容不可以超过上限容量

释放

除了非池化的堆ByteBuf,JVM可以对他进行自动垃圾回收,其他的ByteBuf都需要手动回收
Netty中的所有ByteBuf都实现了ReferenceCounted接口,用于实现引用记数法来控制内存回收

  • 每个ByteBuf计数初始为1
  • 调用release方法计数减1,如果计数为0,则ByteBuf被回收
  • 调用Channel.writeInboud(ByteBuf)后,ByteBuf会被release一次!
  • 调用retain方法计数加1,表示调用者没用完之前,其他handler就算调用了release方法也不会造成回收
  • 当计数为0时,底层内存会被回收,这时即使ByteBuf对象还在,但所有方法都无法正常使用
  • pipeline中的headtail节点会自动释放传递到它们这的ByteBuf,但没传递到他们这的ByteBuf他们无法进行释放

综上所述:我们应该在最后一个使用到ByteBuf的handler中,手动释放该ByteBuf,通常写在finally块中

切片

//切片
ByteBuf buf = oldBuf.slice(index,lenght);
buf.retain();

//组合切片
CompositeByteBuf buf = ByteBufAllocator.DEFAULT.compositeBuffer();
buf.addComponents(true,oldBuf1,oldBuf2);
buf.retain();

通过上述方法可以创建出同一个ByteBuf的多个切片,或多个ByteBuf的一个组合切片,或者说是视图,切片中的数据并不是原ByteBuf数据的复制,使用的是同一块内存,所以切片无法扩展容量。切片的读写指针是独立于原ByteBuf的。

切片和原ByteBuf公用同一引用计数器,一般创建切片后,可以使用切片的retain方法,去增加原ByteBuf的计数,防止原ByteBuf被误释放
例如:

ByteBuf slice1 = buf.slice(0,100);
ByteBuf slice2 = buf.slice(100,200);
channel.writeInboud(slice1);//此时slice1将被调用release,导致slice2和原buf都将被释放
channel.writeInboud(slice2);//slice2已经失效