1. ByteBuf简介
网络上数据的基本单位是字节,Java NIO提供的ByteBuffer作为字节容器并不好用,所以Netty提供了替代品ByteBuf类,具有很多优秀的特性:
- 容量可以按需动态扩展,类似于 StringBuffer;
- 读写采用了不同的指针,读写模式可以随意切换,不需要调用 flip 方法;
- 通过内置的复合缓冲类型可以实现零拷贝;
- 支持引用计数;
- 支持缓存池。
1.1 基本结构
ByteBuf除了保存字节内容外,还会维护2个索引——读索引readerIndex和写索引writerIndex,当从其中读数据时,读索引readerIndex会递增,当写入ByteBuf时,写索引writerIndex会递增。
另外,ByteBuf还维护了废弃字节和可扩容字节这两个属性。ByteBuf默认容量限制为Integer.MAX_VALUE。
1.2 分类维度
ByteBuf都可以划分归属到三个不同的维度中(看不明白可以光看加黑部分,有个概念即可):
- Heap/Direct:堆内和堆外内存。Heap 指的是在 JVM 堆内分配,底层依赖的是字节数据;Direct 则是堆外内存,不受 JVM 限制,分配方式依赖 JDK 底层的 ByteBuffer;
- Pooled/Unpooled:池化和非池化内存。Pooled 是从预先分配好的内存中取出,使用完可以放回 ByteBuf 内存池,等待下一次分配。而 Unpooled 是直接调用系统 API 去申请内存,确保能够被 JVM GC 管理回收;
- Unsafe/非Unsafe:操作方式是否安全。Unsafe 表示每次调用 JDK 的 Unsafe 对象操作物理内存,依赖 offset + index 的方式操作数据。非 Unsafe 则不需要依赖 JDK 的 Unsafe 对象,直接通过数组下标的方式。
Netty通过ByteBufAllocator分配器来创建ByteBuf,分配器有两种实现:
- PoolByteBufAllocator:池化ByteBuf分配器,将ByteBuf实例放入内存池中,提高了性能,将内存碎片减少到最小。它底层采用了jemalloc高效内存分配的策略,该策略被好几种现代操作系统所采用;
- UnpooledByteBufAllocator:普通未池化ByteBuf分配器,它没有把ByteBuf放入内存池中,每次被调用时,返回一个新的ByteBuf实例,通过Java的垃圾回收机制回收。
1.3 池化和非池化
为什么Netty要使用池化技术,内容实在太多,有兴趣的可以看这一篇:
Java中看内存分配—Netty内存池