Netty
作为一个优秀网络框架,其高效的内存操作也是使其变得高性能的很重要原因之一。
众所周知,Java
的NIO
中提供了类ByteBuffer
作为字节的容器,但是操作非常的复杂,Netty针对ByteBuffer
设计了一个替代类ByteBuf
,方便开发者操作字节。
ByteBuf API
对于任意一个ByteBuf
对象,都拥有三个非常重要的属性:
- readerIndex:读索引
- writerIndex:写索引
- capacity:对象容量
ByteBuf
对象每读取一个byte的数据,readerIndex就会往前推进,直到readerIndex到达capacity的值,所有的数据的数据都被读取完,ByteBuf
不可再被读取。可以通过readableBytes()
方法获取readerIndex
的值。
相同地,writerIndex记录了ByteBuf
对象使用了多少数据,可以通过writableBytes()
方法获取writerIndex的值。每当ByteBuf
被写入了多少数据,writerIndex就会往前推进,直到值到达capacity的值,ByteBuf
会自动对空间进行扩容。
对于任意一个ByteBuf
对象,我们都可以根据它的索引通过getByte()
方法随机访问中间的数据。随机访问不会改变readerIndex
的值。
通过array()
方法可以直接获取,ByteBuf
中的Byte数组信息。
几种ByteBuf模式
Netty的“Zero-Copy”设计非常出名,这主要就是依赖了Netty中ByteBuf
的设计。ByteBuf
主要有以下几种模式:
1.Heap Buffer模式
顾名思义,这个模式下的字节是在Jvm的堆区操作的,也是最常见的内存操作了。
2.Direct Buffer模式
在JDK1.4中,Java引入了一种直接内存,NIO可以通过本地方法
分配一些堆外的直接内存,这块内存区不受Jvm的控制,理论上的无限的。
对于网络Socket通信来说,这种内存区域的好处是Java在通信中,数据不必从Jvm中拷贝一份到系统的直接内存区上,操作系统的Socket接口可以直接处理这份在直接内存的数据。同时由于数据在堆外,也避免了频繁GC对这块区域的影响。
ByteBuf
提供了Direct Buffer
模式,我们可以直接通过ByteBuf
操作直接内存。
Direct Buffer
模式下,由于数据不在堆上面,ByteBuf
是不可以直接使用array()
方法获取数据的。
3.Composite buffer模式
在TCP协议中,一份完整的数据总是被拆成好几个包被发送或者接收,一般情况下,程序会通过内存拷贝的方式将一组数据拷贝到一个大的数组中,形成一份完整的数据。
而Composite buffer模式可以聚合多个ByteBuffer对象,将这组数据的引用收集到一个ByteBuf
对象中,避免了数据的拷贝。
// 初始化一个Composite buffer模式的`ByteBuf`
CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer(size);
// 添加byteBuf对象
compositeByteBuf.addComponent(byteBuf1);
compositeByteBuf.addComponent(byteBuf2);
compositeByteBuf.addComponent(byteBuf3);
// 操作compositeByteBuf
handle(compositeByteBuf);
分配内存的方式
当然为了避免Netty本身内存使用过度,Netty内部对所有的内存做了池化。通过ByteBufAllocator
类,我们可以分配一块被池化的内存,从而减少分配和释放内存的开销。
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer();
ByteBuf buffer = ByteBufAllocator.DEFAULT.ioBuffer();
ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer();
ByteBuf buffer = ByteBufAllocator.DEFAULT.compositeBuffer();
如果我们希望使用一块新的内存,或者对一个已经存在的内存进行包装,那么我们可以使用Unpooled
类来分配内存:
ByteBuf heapBuffer = buffer(128);
ByteBuf directBuffer = directBuffer(256);
ByteBuf wrappedBuffer = wrappedBuffer(new byte[128], new byte[256]);
ByteBuf copiedBuffe r = copiedBuffer(ByteBuffer.allocate(128));