Netty的ByteBuf
Netty实现了自己的ByteBuf,是对JDK的ByteBuffer的优化。
JDK的ByteBuffer的缺点:
- 无法动态扩容。若数据大于ByteBuffer容量,会发生索引越界异常。
- API使用复杂。需手工调用flip()和rewind()等方法。
Netty的ByteBuffer做了以下四个方面的增强:
- API操作便捷性
- 动态扩容
- 多种ByteBuf实现
- 高效的零拷贝机制
ByteBuf操作
ByteBuf有三个重要属性:capacity容量、readerIndex读取容量、writeIndex写入位置。同时提供了两个指针变量来支持顺序读和写操作,分别是readerIndex和写操作writerIndex。
缓冲区如下图:
capacity表示是容量。readerIndex指针左边的是已读可丢弃的区域,表示已经读取过一遍,可以丢弃了。而readerIndex和writerIndex指针之间的是可读区域。writerIndex右边的则是尚未被写入的待写区域。
ByteBuf动态扩容
capacity默认值为256字节,其最大值为2GB。 在Netty调用写方法的时候,会通过AbstractByteBufAllocator.calculateNewCapacity(新capacity最小要求,capacity最大值)进行判断。
根据对新的capacity的最小值要求,其对应的计算方法有: 4M为一个固定的阈值:CALCULATE_THRESHOLD。
没超过4M
从64字节开始,每次增加一倍,直至计算出来的newCapacity满足新容量最小要求。
超过4M
新容量 = 新容量最小要求/4M* 4M + 4M
至于为什么这么扩容,别问我,我不知道。
多种ByteBuf实现
有8种具体的实现
在使用中,都是通过ByteBufAllocator分配器进行申请,同时,分配器具备有内存管理的功能。
在Netty里面常用PooledUnsafeDirectByteBuf,一切都是为了快,三秒真男人。
PooledByteBufAllocator.ioBuffer运作过程:
每个线程都有一个对应的PoolThreadCache,里面有一个Arena负责buf的分配管理。先拿一个ByteBuf对象。从可复用对象池RECYCLER里面拿一个ByteBuf,若没有则创建。拿到对象之后就要为之分配内存。每一个线程在对应的缓冲中维护了三大块内存缓存,如果要存放一个小于512byte的数据, 就回到对应的缓存块。
如果没有可用缓存,就直接向JVM申请新的缓存unpool,不会进入缓存池中。
零拷贝机制
Netty的零拷贝机制,是一种应用层的实现。与底层JVM和操作系统没有关系。
1、CompositeByteBuf,将多个ByteBuf合并为一个逻辑上的ByteBuf,从而避免各个ByteBufe之间的拷贝。Netty提供了一个虚拟的复合缓冲区。这个缓冲区内部依然是两个独立的buffer,但是在逻辑上已经是一体的。
CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer();
ByteBuf newBuffer = compositeByteBuf.addComponents(true,buffer1,buffer2);
2、wrapedBuffer()方法,将byte[]数据包装成ByteBuf对象。
对于数组array内的数据不会进行变动,而仅仅是在buffer里的memory里面引用array。
ByteBuf newBuffer = Unpooled.wrappedBuffer(new byte[{1,2,3}]);
3、slice()方法,将一个ByteBuf对象切分成多个ByteBuf对象。
比如已有一个buffer记录着"hello",我们要调取其中的"ll",拆分出来,新的buffer引用原来的原有的buffer的“ll”的引用。
ByteBuf buffer1 = Unpooled.wrappedBuffer("hello".getBytes());
ByteBuf newBuffer = buffer1.slice(1,2);