创建ByteBuf可以使用ctx.alloc().buffer()方法
直接内存 VS 堆内存
获取直接内存的ByteBuf:ByteBuf buffer = ByteBufAllocator.*DEFAULT*.buffer();
获取堆内存的ByteBuf:ByteBuf buffer2 = ByteBufAllocator.*DEFAULT*.heapBuffer();
- 直接内存创建和销毁的代价昂贵,但读写性能高(少一次内存复制),适合池化功能一起用
- 直接内存对GC压力小,因为这部分内存不受JVM垃圾回收的管理,但也要注意及时主动释放
📢 调试日志工具方法
private static void log(ByteBuf byteBuf){ int length = byteBuf.readableBytes(); int rows = length / 16 + (length % 15 == 0 ? 0 :1)+4; StringBuilder buf = new StringBuilder(rows * 80 * 2) .append("read index:").append(byteBuf.readerIndex()) .append(" write index:").append(byteBuf.writerIndex()) .append(" capacity:").append(byteBuf.capacity()) .append(NEWLINE); appendPrettyHexDump(buf,byteBuf); System.out.println(buf.toString()); }
池化 VS 非池化
通过配置环境参数:可以把池化功能去除
Dio.netty.allocator.type=unpooled
池化功能关闭:
UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf :直接内存
UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf :堆内存
池化功能开启:
PooledUnsafeDirectByteBuf :直接内存 PooledUnsafeHeapByteBuf :堆内存
retain & release
Netty中有堆外内存的ByteBuf实现,堆外内存最好是手动释放,
- UnpooledHeapByteBuf使用的是JVM内存,只需要等GC垃圾回收内存即可
- UnpoolledDirectByteBuf使用的就是直接内存了,需要特殊的方法来回收内存
- PooledByteBuf和它的子类使用了池化机制,需要更复杂的规则来回收内存
Nytty采用了引用计数器法来控制回收内存,每个ByteBuf都实现了ReferenceCounted接口
- 每个ByteBuf对象的初始计数为1
- 每个ByteBuf方法计数减1,如果计数为0,ByteBuf,ByteBuf内存被回收
- 调用retain方法计数加1,表示调用者没用完之前,其他handier即使调用了release也不会造成回收
- 当计数为0时,底层内存会被回收,这时即使ByteBuf对象还在,其各个方法均无法正常使用
调用回收时一般是在最后一个使用ByteBuf的handier使用后调用回收释放内存
谁是最后使用者,谁负责release
ByteBuf常用方法
方法签名 | 含义 | 备注 |
---|---|---|
writeBoolean(boolean value) | 写入Boolean值 | 用一字节01 |
writeByte(int value) | 写入byte值 | |
writeInt(int value) | 写入int值 | Big Endian,即0 * 250,写入后00 00 02 50 |
writeIntLE(int value) | 写入int值 | Little Endian,即0 *250,写入后50 02 00 00 |
writeBytes(ByteBuf src) | 写入nio的ByteBuffer | |
writeCharSequence(CharSequence sequence, Charset charset) | 写入字符串,需要指明字符集 | |
buffer.readByte() | 读取一个字节 |
注意
- 这些方法的末尾指明返回值的,其返回都是ByteBuf,意味着可以链式调用
- 网络传输,默认习惯是Bug.endian
slice
【零拷贝】的体现之一,堆原始ByteBuf进行切片成多个ByteBuf,切片后的ByteBuf并没有发生内存复制,还是使用原始ByteBuf的内存,切片后的ByteBuf维护独立的read,write指针
public static void main(String[] args) {
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(10);
buf.writeBytes(new byte[]{1,2,3,4,5,6,7,8,9,0});
// 切片过程中,不会发生数据复制
ByteBuf buf1 = buf.slice(0, 5);
ByteBuf buf2 = buf.slice(5, 5);
buf1.setByte(0,'c');
buf1.retain();
log(buf1);
log(buf2);
buf1.release();
log(buf);
🐥
- slice后新的ByteBuf实际使用的还是原来的内存地址,使用如果使用release()后把内存清除掉,即slice()切片后的ByteBuf也无法使用;
- 切片后的添加元素不能大于切片出来的容量。