目录
目标
概述
实战
创建直接内存的ByteBuf和堆内存的ByteBuf
创建池化的ByteBuf和非池化的ByteBuf
扩容ByteBuf
ByteBuf写出方法
ByteBuf读入方法
释放ByteBuf的内存
修改ByteBuf
对ByteBuf进行切片(逻辑上的切分)
复制ByteBuf(物理上的)
组合多个ByteBuf
池化的ByteBuf和非池化的ByteBuf的区别
类比数据库连接池,池化的ByteBuf可以被重用,对高并发有很好的节约内存的效果。4.1以前的版本默认创建非池化ByteBuf,4.1以后的版本默认创建池化的ByteBuf,但是Android默认创建非池化的ByteBuf。
直接内存的ByteBuf和堆内存的ByteBuf的区别
直接内存的ByteBuf创建和销毁代价大,但是读写性能高,因为少了一次内存复制。减少了垃圾回收机制的压力。
ByteBuf扩容规律
ByteBuf可以指定初始容量也可不指定。不指定初始容量,则容量默认256个字节大小。数据超过容量以后ByteBuf会自动扩容。扩容规则如下:
推荐创建ByteBuf的方法
在Pipeline中创建ByteBuf时推荐使用ChannelHandlerContext,如:
pipeline.addLast("InboundHandler2", new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buffer = ctx.alloc().buffer(10);
}
});
/**
* 创建基于直接内存和堆内存的ByteBuf
* 直接内存创建和销毁代价大,但是读写速度快。
*/
public void directAndHeap(){
//直接内存
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer();
System.out.println(byteBuf.getClass());
//堆内存
ByteBuf byteBuf1 = ByteBufAllocator.DEFAULT.heapBuffer();
System.out.println(byteBuf1.getClass());
}
输出结果
第一步:以idea为例,需要做一下配置。
第二步
第三步:如图所示,需要将VM options设置为-Dio.netty.allocator.type=unpooled
第四步:创建ByteBuf并输出ByteBuf类名,此时发现ByteBuf变成了非池化类型。需要注意,ByteBuf默认创建池化类型。
/**
* ByteBuf扩容案例
* ByteBuf可以指定初始容量也可不指定。不指定初始容量,则容量默认256个字节大小。
* 数据超过容量以后ByteBuf会自动扩容。扩容规则如下:
* 扩容小于等于512,扩容为16的倍数大小。
* 扩容大于512,扩容为2的n次方大小。
*/
public void addCapacity(){
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
System.out.println(buffer);
StringBuffer sb = new StringBuffer();
//添加257个字节大小的字符串。
for(int i=0;i<257;i++){
sb.append("a");
}
buffer.writeBytes(sb.toString().getBytes());
System.out.println(buffer);
}
/**
* ByteBuf扩容案例
* ByteBuf可以指定初始容量也可不指定。不指定初始容量,则容量默认256个字节大小。
* 数据超过容量以后ByteBuf会自动扩容。扩容规则如下:
* 扩容小于等于512,扩容为16的倍数大小。
* 扩容大于512,扩容为2的n次方大小。
*/
public void addCapacity2(){
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
System.out.println(buffer);
StringBuffer sb = new StringBuffer();
//添加513个字节大小的字符串。
for(int i=0;i<513;i++){
sb.append("a");
}
buffer.writeBytes(sb.toString().getBytes());
System.out.println(buffer);
}
public void writeTest() {
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.heapBuffer();
//写入byte[]
byte[] bytes =new byte[]{'a', 'b', 'c'};
byteBuf.writeBytes(bytes);
//写入整数
byteBuf.writeInt(1);
//写入long
byteBuf.writeLong(1L);
//写入字符串
byteBuf.writeCharSequence("Hello World!",Charset.forName("UTF-8"));
//写入StringBuffer
StringBuffer sb = new StringBuffer();
byteBuf.writeCharSequence(sb,Charset.forName("UTF-8"));
//写入java.nio.ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(8);
byteBuf.writeBytes(buffer);
}
/**
* 循环读取ByteBuf,读取过程中移动读指针。
*/
public void circulateReadTest(){
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.heapBuffer();
byteBuf.writeCharSequence("Hello World!",Charset.forName("UTF-8"));
//循环读取ByteBuf,观察读指针(ridx)的变化情况。
System.out.println(byteBuf);
for(int i=byteBuf.readerIndex();i
概述
ByteBuf实现了io.netty.util.ReferenceCounted接口,采用了引用计数的方法来标记ByteBuf对象的使用情况。其中:
ByteBuf调用release()需要特别注意
第一:并不是在每个Handler的最后调用release(),而是最后一个使用ByteBuf的Handler负责计数减1。因为ByteBuf可能在多个Handler中传递,一旦每个Handler都负责计数减1,则下一个Handler就可能因为ByteBuf的底层内存被回收而无法使用。
第二:虽然Pipeline自带头部和尾部Handler,且会回收ByteBuf的内存,但前提是ByteBuf必须要流转至头部或者尾部,内存才会被回收。比如Handler_1将ByteBuf传递给Handler_2,Handler_2将ByteBuf转成String传递给头部和尾部Handler,则ByteBuf所在内存仍然没有被回收。
源码跟进
尾部Handler对对象计数减1的源码
io.netty.channel.ChannelPipeline接口
io.netty.channel.DefaultChannelPipeline类
io.netty.channel.DefaultChannelPipeline.TailContext类的channelRead(ChannelHandlerContext ctx, Object msg)方法
再跟进到了onUnhandledInboundMessage(Object msg)方法,该方法的内部有ReferenceCountUtil.release(msg)负责计数减1
进入release(msg),发现对象必须要实现io.netty.util.ReferenceCounted接口,对象计数才会被减1
public static boolean release(Object msg) {
return msg instanceof ReferenceCounted ? ((ReferenceCounted)msg).release() : false;
}
头部Handler对对象计数减1的源码
io.netty.channel.ChannelPipeline接口
io.netty.channel.DefaultChannelPipeline类
io.netty.channel.DefaultChannelPipeline.HeadContext类的write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)方法
再跟进到了io.netty.channel.AbstractChannel类的write(Object msg, ChannelPromise promise)方法,当没有出栈缓存时进入计数减1操作,同样地,进入release(msg)后发现对象必须要实现io.netty.util.ReferenceCounted接口,对象计数才会被减1
public final void write(Object msg, ChannelPromise promise) {
this.assertEventLoop();
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
if (outboundBuffer == null) {
try {
ReferenceCountUtil.release(msg);
} finally {
this.safeSetFailure(promise, this.newClosedChannelException(AbstractChannel.this.initialCloseCause, "write(Object, ChannelPromise)"));
}
} else {
int size;
try {
msg = AbstractChannel.this.filterOutboundMessage(msg);
size = AbstractChannel.this.pipeline.estimatorHandle().size(msg);
if (size < 0) {
size = 0;
}
} catch (Throwable var15) {
try {
ReferenceCountUtil.release(msg);
} finally {
this.safeSetFailure(promise, var15);
}
return;
}
outboundBuffer.addMessage(msg, size, promise);
}
}
public static boolean release(Object msg) {
return msg instanceof ReferenceCounted ? ((ReferenceCounted)msg).release() : false;
}
/**
* 根据下标修改ByteBuf元素
*/
public void updateTest(){
//创建一个初始容量为10字节大小的ByteBuf
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer(10);
byteBuf.writeBytes(new byte[]{'1','2','3','4','5','6','7','8','9','0'});
//修改下标为0的元素为'2'
byteBuf.setByte(0,'2');
for(int i=0;i
/**
* 切分ByteBuf
*/
public void sliceTest(){
//创建一个初始容量为10字节大小的ByteBuf
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer(10);
byteBuf.writeBytes(new byte[]{'1','2','3','4','5','6','7','8','9','0'});
//从下标2开始切分,切取5个长度。
ByteBuf sliceByteBuf = byteBuf.slice(2, 5);
for(int i=0;i
特别注意
推荐案例
public void sliceTest(){
//创建一个初始容量为10字节大小的ByteBuf
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer(10);
byteBuf.writeBytes(new byte[]{'1','2','3','4','5','6','7','8','9','0'});
//从下标2开始切分,切取5个长度。
ByteBuf sliceByteBuf = byteBuf.slice(2, 5);
//引用计数加一。
sliceByteBuf.retain();
//将原始的ByteBuf释放掉。
byteBuf.release();
//发现原来的ByteBuf还可以继续使用。
for(int i=0;i
/**
*copy()方法对ByteBuf进行了深拷贝,是物理上的复制,与原来的ByteBuf无关联。
*/
public void copyTest(){
//创建一个初始容量为10字节大小的ByteBuf
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer(10);
byteBuf.writeBytes(new byte[]{'1','2','3','4','5','6','7','8','9','0'});
//复制整个原始的ByteBuf。
ByteBuf cp =byteBuf.copy();
cp.setByte(0,'b');
for(int i=0;i
/**
* 组合多个ByteBuf
* 方法1:通过writeBytes方法组合多个ByteBuf。新组合的ByteBuf与之前的ByteBuf完全独立开。
*/
public void writeBytesTest(){
ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer(5);
byteBuf.writeBytes(new byte[]{'1','2','3','4','5'});
ByteBuf byteBuf2 = ByteBufAllocator.DEFAULT.directBuffer(5);
byteBuf2.writeBytes(new byte[]{'6','7','8','9','0'});
//
ByteBuf byteBuf3 = ByteBufAllocator.DEFAULT.directBuffer(10);
byteBuf3.writeBytes(byteBuf).writeBytes(byteBuf2);
for(int i=0;i