目录
ByteBuf-Netty的数据容器
API详细信息
用例
内存分配
ByteBuffer
: JAVA NIO使用的字节容器,使用复杂;ByteBuf
: Netty提供的字节容器,更简单灵活。
ByteBuf API
优点
- 用户可以自定义缓冲区类型扩展
- 透明的零拷贝
- 容量按需增长
- 读写切换不需要
flip()
方法 - 读写使用不同索引
- 支持方法链式调用
- 支持引用计数
- 支持池化
外部接口:
- 从
Channel
或者ctx
获取ByteBuffAllocator
,然后再分配; - 用
Unpooled
工具类直接创建。1
2
3
4
5
6
7
8
9
10// 1. 从channel:
Channel channel = ...;
ByteBufAllocator allocator = channel.alloc(); //1
ByteBuf buf=allocator.heapBuffer();
// 2. 从ctx:
ChannelHandlerContext ctx = ...;
ByteBufAllocator allocator2 = ctx.alloc(); //2
ByteBuf buf=allocator.heapBuffer();
// 3.
ByteBuf buf=Unpooled.buffer();
内部工作机制
维护两个索引: readerIndex
,writerIndex
// 这样就省得用flip
来回切换读写状态了。
如上图,起始为空,读的时候发现readerIndex
>=writerIndex
,因此失败。
使用模式
堆缓冲区-使用模式 (也称支撑数组backing array)
(heap buffer)
最常用,将数据存储在JVM
堆中。
1 | private final static Random random = new Random(); |
直接缓冲区-使用模式
(direct buffer)
堆外内存,directBuffer
,性能最好,省去一次复制。
缺点时要注意管理堆外内存。
1 | public static void directBuffer() { |
复合缓冲区-使用模式
(composite buffer)
把上述两个模式复合,把多个ByteBuf
聚合成一个视图处理。
消除了没有必要的复制,同时对外是一个通用的接口。
1 | public static void byteBufComposite() { |
CompositeByteBuf
附加了许多功能。
字节级操作
随机访问 (可以重复消费)(getXXX之类的方法)
ByteBuf
的随机访问与普通数组类似:
1 | for(int i;i<buffer.capacity();i++){ |
普通访问不会改变readerIndex
和writerIndex
。
要改变需要调用readerIndex(index)
和writerIndex(index)
。
顺序访问 (不会重复消费)(readXXX/skipXXX之类的方法)
如上图所示,这种访问方式可以看作消费数据,通过移动readerIndex,扩大了可丢弃字节(已读),减少了可读字节。
1 | //迭代缓冲区的可读字节。 |
丢弃discardable bytes(类似于compact/gc)
1 | //调用: |
丢弃已读部分回收空间,本质上是扩大了可写字节。
底层的实现是把可读的数据移动到首部,readerIndex=0;writerIndex-=readerIndex.
,因此导致内存复制,有性能开销,所以尽量不要调用。
顺序写入:
1 | // 1. |
索引修改
readerIndex
和writerIndex
这俩变量,除了因为上述数据操作而间接发生改变,也可以直接强行改它们而不管数据:
1 | markReaderIndex()// 标记为x |
这些操作不改变数据,所以是O(1)的。
查找(类似于Scan And Filter操作)
1 | //1: |
派生缓冲区(类似于视图,可读写) // 浅拷贝
创建的方法:
1 | duplicate() |
这些派生的ByteBuf
实例,底层数据是和原来共享的,只是有自己的读写索引。
深拷贝
1 | copy(int,int) |
ByteBufHolder接口
ByteBufHolder
接口就是把ButeBuf
再封装一层,只暴露几个大的API:
1 | ByteBuf content()// 返回持有的ByteBuf |
这样只需要声明实现这个接口,相当于宣布这个类内部有成员变量是ByteBuf
,换言之就是一个承载数据的对象了。
ByteBuf分配
按需分配: ByteBufAllocator接口
从channel
或者ctx
获得ByteBufAllocator
,降低分配和释放内存的开销。
1 | // 1. 从channel: |
拿到分配器以后,可以进行的操作:
1 | Buffer() |
netty的两个ByteBufAllocator
的实现:
1 | PooledByteBufAllocator // 池化的,性能最高 |
Channel
具体返回的分配器是池化还是非池化,可以通过ChannelConfig
配置,或者设定bootstrap
参数。
Unpooled缓冲区
非网络项目时,或其他不能从channel
,ctx
获取BufAllocator
时,可以用Unpooled
工具类来创建未池化的ByteBuf
实例。
它的方法与上述两个类似,只不过去掉了heapBuf
,用buffer()
方法代替,默认直接返回堆内存buf。
ByteBufUtil类
顾名思义,有一些用于ByteBuf
的辅助方法,看起来比较有用包括:
1 | firstIndexof |
引用计数 // ReferenceCounted
由于ByteBuf
一般比较大,所以实际数据一般只会存一份,引用却会有很多。
然后引用的生命周期一般很长,。
所以netty
除了gcroot
的方法管理,还用了原始的引用计数:ReferenceCounted
接口。
上文中的ByteBufHolder
接口就是它的子接口。
1 | ByteBufHolder => ReferenceCounted |
四种ByteBuf
:
1 | UnpooledHeapByteBuf: 堆内存,可以被jvm gc; |
换言之:
池化的jvm不管;
非池化的jvm可以管。
增加计数器的操作:
1 | retain() |
减少计数器的操作:
1 | release() |
编程规范:
- pipeline里最后一个处理者要负责release()
- 中间某一个抛异常了,不往下传了,也要负责release()。
- 中间某一个往下传了别的数据,要负责release传入的原来那个数据。
内存泄露提示:
1 | LEAK: ByteBuf.release() was not called before it’s garbage-collected. Enable advanced leak reporting to find out where the leak occurred. To enable advanced leak reporting, specify the JVM option ‘-Dio.netty.leakDetectionLevel=advanced’ or call ResourceLeakDetector.setLevel() |
出现了这个说明有地方没有release()
.可以提高检测级别打印出具体泄露点。
参考:
http://chen-tao.github.io/2015/10/03/netty/
http://calvin1978.blogcn.com/articles/directbytebuffer.html