Netty组件 之 ByteBuf
是对字节数据的封装
1)创建
ByteBuf buffer = ByteBufAllocator.Default.buffer(10)
log(buffer)
上面代码创建了一个默认的ByteBuf(池化基于直接内存的ByteBuf),初始容量是10
输出
read index:0 write index:0 capacity:10
其中log方法参考如下:
private static void log(ByteBuf buffer){
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
.append(NEWLINE);
appendPrettyHexDump(buf,buffer);
System.out.println(buf.toString());
}
2)直接内存 vs 堆内存
可以使用下面代码来创建池化 基于 堆的ByteBuf
ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(10);
也可以使用下面的代码来创建池化基于直接内存的ByteBuf
ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10);
- 直接内存创建和销毁的代价昂贵,但读写性能高(少一次内存复制),适合配合池化功能一起用
- 直接内存对GC压力小,因为这部分内存不受JVM垃圾回收管理,但也要注意及时主动释放
3)池化vs非池化
池化的最大意义在于可以重用ByteBuf,优点有
- 没有池化,则每次都得创建新的ByteBuf实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加GC压力。
- 有了池化,则可以重用池中ByteBuf实例,并且采用了与Jemalloc类似的内存分配算法提升分配效率
- 高并发时,池化功能更节约内存,减少内存溢出的可能
池化功能是否开启,可以通过下面的系统环境变量来设置
-Dio.netty.allocator.type={unpooled|pooled}
- 4.1 以后,非Android平台默认启用池化实现,Android启用非池化实现
- 4.1 之前,池化功能还不成熟,默认是非池化实现
4)组成
ByteBuf 由四部分组成
最开始读写指针都在0 位置
5)写入
先写入4个字节
buffer.writeBytes(new byte[]{1,2,3,4});
log(buffer)
结果是
read index:0 write index:4 capacity:10
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04 |.... |
+--------+-------------------------------------------------+----------------+
再写入一个int整数,也是4个字节
buffer.writeInt(4);
log(buffer);
结果是
read index:0 write index:8 capacity:10
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04 00 00 00 05 |........ |
+--------+-------------------------------------------------+----------------+
6)扩容
再写入一个int整数时,容量不够了(初始容量是10),这时会引发扩容
buffer.writeInt(6);
log(buffer)
扩容规则是:
- 如何写入后数据大小未超过512,则选择下一个16的整数倍,例如写入后大小为12,则扩容后capacity是16
- 如果写入后数据大小超过512,则选择下一个2^n,例如写入后大小为513,则扩容后capacity是 210=1024(29=512 已经不够了)
- 扩容不能超过 max capacity 会报错
结果是
read index:0 write index:12 capacity:64
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04 00 00 00 05 00 00 00 06 |............ |
+--------+-------------------------------------------------+----------------+
7)读取
例如读了4次,每次一个字节
System.out.println(buffer.readByte());
System.out.println(buffer.readByte());
System.out.println(buffer.readByte());
System.out.println(buffer.readByte());
log(buffer);
读过的内容,就属于废弃部分了,再读只能读那些尚未读取的部分
1
2
3
4
read index:4 write index:12 capacity:64
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 05 00 00 00 06 |........ |
+--------+-------------------------------------------------+----------------+
如果需要重复读取int整数5,怎么办?
可以在read前先做个标记 mark
buffer.markReaderIndex();
System.out.println(buffer.readInt());
log(buffer);
结果
5
read index:8 write index:12 capacity:64
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 06 |.... |
+--------+-------------------------------------------------+----------------+
这时要重复读取的话,重置到标记位置reset
buffer.resetReaderIndex();
log(buffer)
结果
read index:4 write index:12 capacity:64
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 05 00 00 00 06 |........ |
+--------+-------------------------------------------------+----------------+
8)retain & release
由于Netty 中有堆外内存的ByteBuf 实现,堆外内存最好是手动来释放,而不是等GC垃圾回收。
- UnpooledHeapByteBuf 使用的是JVM内存,只需等GC回收内存即可。
- UnpooledDirectByteBuf 使用的就是直接内存,需要特殊的方法来回收内存。
- PooledByteBuf 和 它的子类使用了池化机制,需要更复杂的规则来回收。
回收内存的源码实现,请关注下面方法的不同实现
protected abstract void deallocate()
Netty这里采用了引用计数法来控制回收内存,每个ByteBuf都实现了ReferenceCounted接口
- 每个ByteBuf 对象的初始计数为1
- 调用release 方法计数减1, 如果计数为0,ByteBuf内存被回收。
- 调用retain 方法计数加1,表示调用者没用完之前,其它handler即使调用了release 也不会造成回收。
- 当计数为0时,底层内存会被回收,这时即使ByteBuf对象还在,其各个方法均无法正常使用。
9)slice
【零拷贝】的体现之一,对原始ByteBuf进行切片或多个ByteBuf,切片后的ByteBuf并没有发生内存复制,还是使用原始ByteBuf 的内存,切片后的ByteBuf 维护独立的 read,write 指针
例,原始ByteBuf 进行slice操作
ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(10);
buf.writeBytes(new byte[]{'a','b','c','d','e','f','g','h','i','j'});
ByteBuf slice1 = buf.slice(0, 5);
ByteBuf slice2 = buf.slice(5, 5);
log(slice1);
log(slice2);
结果
read index:0 write index:5 capacity:5
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 65 |abcde |
+--------+-------------------------------------------------+----------------+
read index:0 write index:5 capacity:5
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 66 67 68 69 6a |fghij |
+--------+-------------------------------------------------+----------------+
10)duplicate
【零拷贝】的体现之一,就好比截取了原始ByteBuf所有内容,并且没有max capacity的限制,也是与原始ByteBuf使用同一块底层内存,只是读写指针是独立的。
11)copy
会将底层内存数据进行深拷贝,因此无论读写,都与原始ByteBuf无关。
12)composite
将多个小的 ByteBuf 组合成一个大的ByteBuf
ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer();
buf1.writeBytes(new byte[]{1,2,3,4,5});
ByteBuf buf2 = ByteBufAllocator.DEFAULT.buffer();
buf2.writeBytes(new byte[]{6,7,8,9,10});
//效率低 需要将buf1 buf2的 数据复制到buffer中
// ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
// buffer.writeBytes(buf1).writeBytes(buf2);
// log(buffer);
//效率高 避免了内存的复制
CompositeByteBuf buffer = ByteBufAllocator.DEFAULT.compositeBuffer();
buffer.addComponents(true,buf1,buf2);
log(buffer);
结果
read index:0 write index:10 capacity:10
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 01 02 03 04 05 06 07 08 09 0a |.......... |
+--------+-------------------------------------------------+----------------+