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中的head和tail节点会自动释放传递到它们这的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已经失效