chapter15 ByteBuf和相关辅助类
1、ByteBuf诞生背景:
- NIO中,除了Boolean外,其他基础类型都有自己的缓冲区实现,但主要使用的是ByteBuffer,但其存在以下主要缺点:
- ByteBuffer长度固定,编码大POJO时,容易发生越界异常
- ByteBuffer只有一个标识位置的指针position,读写的时候需要手工调用flip()和rewind()等,使用需要谨慎处理
- ByteBuffer的API功能有限,一些高级和实用的特性它不支持
- 因此Netty提供了自己的ByteBuffer实现——ByteBuf。
2、ByteBuf的工作原理
- 基本功能:
- 7种java基础类型、byte数组、ByteBuffer(ByteBuf)等的读写;
- 缓冲区自身的copy和slice等;
- 设置网络字节序;
- 构造缓冲区实例;
- 操作位置指针等方法;
- Netty实现策略:
- 参考ByteBuffer的实现,增加额外的功能,解决原ByteBuffer的缺点;
- 聚合ByteBuffer,通过facade模式对其包装,可以减少自身代码量,降低实现成本。
ByteBuffer中有position、limit、capacity,ByteBuf定义了readerIndex、writerIndex、capacity,将读写分开简化了操作。
ByteBuf动态扩展的实现:
- 对write操作进行了封装,由write操作负责进行剩余可用空间的校验,如果缓冲不够,ByteBuf自动进行扩展,使用者不需要关系校验和扩展的逻辑,只要不超过设置的最大缓冲区容量即可
3、ByteBuf功能介绍
- 顺序读操作(read)
- 类似于ByteBuffer的get操作。
- 顺序写操作(write)
- 类似于ByteBuffer的put操作。
- readerIndex和writerIndex
- 0 <= readerIndex <= writerIndex <= capacity
- 0到readerIndex为已经读取过的缓冲区,可以调用discardReadBytes操作来重用这部分空间,节约内存,防止ByteBuf的动态扩张,非常适合用于解决TCP沾包问题。
- Discardable bytes
- 降低性能换取内存操作,因为discardReadBytes会发生子节数组的内存复制,所以,频繁调用会降低性能。
- Readable bytes和Writable bytes
- 当读取的子节长度大于可读字节数,则会抛出IndexOutOfBoundsException。同理写入的超过可写入的也会抛出该异常。
- Clear操作
- 还原为初始分配值。
- Mark和Rest
- markReaderIndex,将当前的readerIndex备份到markedReaderIndex中
- resetReaderIndex,将当前的readerIndex设置为markReaderIndex
- write同理。
- 查找操作
- Derived buffers
- 创建某个ByteBuf的视图或者复制ByteBuf。
- duplicate、copy、copy(int index,int length)、slice、slice(int index,int length)
- 转成标准的ByteBuffer
- nioByteBuffer()、nioByteBuffer(int index,int length)创建的ByteBuffer都无法感知ByteBuf的动态扩展。
- 随机读写(set和get)
- set操作不会动态扩展缓冲区。
4、ByteBuf源码分析
- 从内存角度,分为两类:
- 堆内存 (HeapByteBuf)
- 特点是分配与回收快,可以被JVM自动回收;缺点就是进行Socket的IO读写,需要额外做一次内存复制,将堆内存复制到对应的Channel中,会降低性能。
- 直接内存(DirectByteBuf)
- 在堆外进行内存分配,分配和回收会慢一些,但是写入Channel中,少了一次内存复制,速度比堆内存快。
- 堆内存 (HeapByteBuf)
- 最佳实践:在IO通信线程的读写缓冲区采用DirectByteBuf,后端业务消息的编码解码模块使用HeapByteBuf。
- 从内存回收角度:
- 基于对象池ByteBuf
- 可以重用ByteBuf,提升内存利用率,降低高负载下导致的频繁GC,适合高负载,高并发。
- 普通ByteBuf
- AbstractByteBuf
- AbstractReferenceCountedByteBuf
- 对引用进行计数,采用CAS
- UnpooleedHeapByteBuf
- 基于堆内存进行内存分配的字节缓冲区,没有基于对象池实现
- PooledByteBuf
- PoolArena
- Arena本身是指一块区域,在内存管理中,Memory Arena是指内存中的一大块连续的区域(提前申请的一大块内存),PoolArena就是Netty的内存池实现。
- 由多个Chunk组成、每个Chunk由一个或者多个Page组成,Page被构建成一颗二叉树。
- PoolChunk
- PoolSubpage
- 内存回收策略
- Chunk和Page都通过状态位来标识内存是否可用。Chunk通过标识二叉树上的节点实现,Page通过维护块的使用状态来实现。
- PooledDirectByteBuf
- 基于内存池实现,与UnPooleedDirectByteBuf唯一不同就是缓冲区的分配和销毁策略不同。
- PoolArena
- 基于对象池ByteBuf
5、ByteBuf相关辅助类
- ByteBufHolder
- ByteBuf的容器,为了满足定制化需求,使用者继承ByteBufHolder接口后可以按需封装自己的实现。
- ByteBufAllocator
- 字节缓冲区分配器,按照Netty的缓冲区实现不同,分为两种:基于内存池的字节缓冲区分配器和普通的字节缓冲区分配器。
- CompositeByteBuf
- 允许将多个ByteBuf的实例组装到一起,形成一个统一的视图。如消息体和消息头
- ByteBufUtil
- 提供了实用的工具类,如encodeString、decodeString、转换16进制的hexDump等。