Netty 学习(八)强大的数据容器 ByteBuf

前言

Netty 大量的使用自己实现的 ByteBuf 工具类,该类提供了比 JDK NIO 包中 ByteBuffer 更优秀的内部实现和 API。

一、为什么选择 ByteBuf

1. ByteBuffer

ByteBuffer 的内部结构:

在这里插入图片描述ByteBuffer 包含的四个基本属性:

  • mark:为某个读取过的关键位置做标记,方便回退到该位置
  • position:当前读写位置
  • limit:buffer 中有效数据的长度大小
  • capacity:初始化是空间容量
    这四个基本属性的关系是:mark <= position <= limit <= capacity

ByteBuffer 的缺陷

  • 长度固定,无法动态扩容
  • 读写共用 position 指针,操作过程中需要频繁调用 flip、rewind 方法切换读写状态

ByteBuf 的新特性

  • 容量可以按需动态扩展
  • 读写采用不同的指针,不需要切换读写模式
  • 支持引用计数
  • 支持缓存池
  • 可以实现零拷贝

二、ByteBuf

1. ByteBuf 内部结构

在这里插入图片描述

ByteBuf 包含的三个指针

  • readerIndex:当前读取的位置
  • writeIndex:当前写入的位置
  • maxCapacity:最大空间容量

ByteBuf 区域划分

  • 已废弃字节:已经废弃的无效字节数据
  • 可读字节:可以被读取的字节内容,writeIndex - readerIndex 计算得出。readerIndex 随 read API 的调用自增。当 readerIndex == writeIndex 时,ByteBuf 不可读
  • 可写字节:向 ByteBuf 写入 N 个字节,writeIndex 就会自增 N。当 writeIndex 超过 capacity,表示 ByteBuf 容量不足,需要扩容。
  • 可扩容字节:writeIndex 大于 capacity 时,会触发 ByteBuf 扩容,最多扩容到 maxCapacity。

2. ByteBuf 引用计数

引用计数用于管理生命周期的一种方法,当 ByteBuf 不被其他对象使用时,引用计数为 0, 那么对象可以被释放;只要引用计数大于 0,表示 ByteBuf 还在被使用。
引用计数对于 Netty 设计缓存池化有很大帮助,让引用计数为 0,该 ByteBuf 可以被放入对象池中,避免重复创建。

内存泄漏检测

ByteBuf 对象不可达时,会被 JVM GC 回收掉。但如果此时 ByteBuf 的引用计数不为 0,那么该对象就不会释放或被放入对象池,从而发送了内存泄漏。Netty 会对分配的 ByteBuf 进行抽样分析,检测是否存在 ByteBuf 对象不可达但引用计数大于 0,并将位置输出到日志中,需要关注日志中的 LEAK 关键字。

三、ByteBuf 分类

ByteBuf 有多种实现类,每种都有不同的特性,下图是 ByteBuf 的家族图谱,可以划分为三个不同的维度:Heap/Direct、Pooled/Unpooled和Unsafe/非 Unsafe。
在这里插入图片描述

  • Heap/Direct:堆内和堆外内存,Heap 指的是在 JVM 堆分配,底层依赖的是字节数据;Direct 则是堆外内存,分配方式是依赖于 JDK 底层的 ByteBuffer。
  • Pooled/Unpooled:池化和非池化内存,Pooled 从预先分配好的内存中取出,使用完成后放回 ByteBuf 内存池;Unpooled 是直接调用 API 去申请内存,确保能被 JVM GC 回收。
  • Unsafe/非 Unsafe:操作方式是否安全,Unsafe 表示每次调用 JDK 的 Unsafe 对象操作物理内存,依赖 offset + index 方式操作数据;非 Unsafe 则不需要依赖 JDK 的 Unsafe 对象,直接通过数组下标方式操作数据。

五、ByteBuf 核心 API

1. 指针操作 API

  • readerIndex() & writeIndex():获取读写位置
  • markReaderIndex() & resetReaderIndex():保存和重置读位置
  • markWriterIndex() 和 resetWriterIndex():保存和重置写位置

2. 数据读写 API

  • isReadable():判断 ByteBuf 是否可读
  • readableBytes():可以读取的字节数,writeIndex - readerIndex
  • readXXX():读取数据,readerIndex 会相应增长
  • getXXX():读取数据,readerIndex 不发生变化
  • writeXXX():写入数据,writeIndex 会相应增长
  • setXXX():更改数据,不改变 writeIndex 指针

3. 内存管理 API

  • release() & retain():每调用一次 release,引用计数减 1; 每调用一次 retain,引用计数加 1
  • slice() & duplicate():slice 截取 readerIndex 到 writerIndex 之间的数据,最大容量为原始 ByteBuf 的可读取字节数;duplicate 截取的是整个原始 ByteBuf 信息。这两个方法底层分配内存、引用计数都与原始的 ByteBuf 共享。
  • copy():从原始 ByteBuf 拷贝所有信息,所有数据都是独立的。

4. ByteBuf API 使用要点:

  • write 系列方法会改变 writeIndex,set 系列方法不会改变。
  • read 系列方法会改变 readerIndex,get 系列方法不会改变。
  • 写入数据长度超过 capacity 时, byteBuf 会尝试扩容,但不能超过 maxCapacity,否则会抛出异常。

总结

Netty 强大的数据容器 ByteBuf,不仅解决了 JDK NIO 中 ByteBuffer 的缺陷,还提供了易用性更强的 API。ByteBuf 可以作为一个独立的库引入项目中,即使在非网络应用中,也可以用来代替 ByteBuffer。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值