Netty 的 ByteBuf 为什么好用?

#中间件#面试八股

Netty 的 ByteBuf 为什么好用?

结论先行:ByteBuf 用读写双指针取代了 ByteBuffer 的 flip() 状态机,支持按需扩容、池化分配、引用计数与堆内/堆外双实现,还能零拷贝切片。它把“缓冲区管理”这件容易出错的事,收敛成 readerIndex 与 writerIndex 两个数字。

一、与 ByteBuffer 的对比

维度ByteBufferByteBuf
读写切换需要 flip(),忘了就出错双指针,读写互不干扰
扩容固定容量,满了只能新建ensureWritable() 自动扩容
池化无PooledByteBufAllocator
内存释放依赖 GC引用计数,可主动释放
切片slice() 但索引独立slice() 共享内存,可选 retain
复合需自己拼接CompositeByteBuf 逻辑合并
API相对底层readInt()、writeBytes() 等链式方法

二、双指针模型

 +--------------------------------------------------+
 | discardable |   readable    |    writable        |
 +--------------------------------------------------+
 0          readerIndex     writerIndex        capacity

readableBytes() = writerIndex - readerIndex
writeXxx()  → writerIndex 右移
readXxx()   → readerIndex 右移
discardReadBytes() → 把可读区左移,回收已读空间
clear()     → 两个指针都归零,不清数据
ByteBuf buf = ctx.alloc().buffer(8);
buf.writeInt(100);           // writerIndex: 0 → 4
buf.writeByte(1);            // writerIndex: 4 → 5

int v = buf.readInt();       // readerIndex: 0 → 4,返回 100
byte b = buf.readByte();     // readerIndex: 4 → 5,返回 1
buf.clear();                 // 复用,不清零内存

因为读写指针独立,ByteBuf 不需要 flip():写完直接交给下游读,读到哪算到哪。

三、扩容与池化

// 演示扩容:初始 4 字节,写入 10 字节自动扩到 16
ByteBuf buf = Unpooled.buffer(4);
buf.writeBytes(new byte[10]);
System.out.println(buf.capacity());   // 64:不足 64 时按 64 起步,之后每次翻倍

// 池化:复用内存,减少 GC 与分配开销
ByteBuf pooled = PooledByteBufAllocator.DEFAULT.directBuffer(1024);
try {
    pooled.writeBytes(data);
    channel.writeAndFlush(pooled.retain());   // 交给 Netty 后由 pipeline 释放
} finally {
    pooled.release();                          // 自己这份引用要还
}

四、引用计数

分配 ──> refCnt=1
retain() ──> refCnt=2
release() ──> refCnt=1     (仍可使用)
release() ──> refCnt=0     (归还池,禁止再访问)
方法是否增加引用谁负责释放
slice()否父 buffer 的持有者
retainedSlice()是调用方
duplicate()否父 buffer 的持有者
writeAndFlush(buf)否(转移所有权)pipeline 末端

五、选堆内还是堆外

类型分配成本读写速度适用
HeapByteBuf低快(JVM 可直接访问数组)业务处理、需要数组操作
DirectByteBuf高(需池化)快(少一次拷贝到内核)网络读写、大报文

常见追问

  • 追问:忘了 release 会怎样?池化内存不归还,表现为堆外内存持续增长直到 OOM,用 -Dio.netty.leakDetection.level=paranoid 定位。
  • 追问:discardReadBytes() 什么时候用?需要复用已读空间时用,但它会移动内存,频繁调用反而降低性能。
  • 追问:ByteBuf 线程安全吗?不安全。它靠 EventLoop 串行访问保证安全,跨线程共享必须自己做同步或复制。

小结

  • 双指针消除 flip(),读写不再互相干扰。
  • 自动扩容 + 池化 + 引用计数,是它优于 ByteBuffer 的核心。
  • slice/duplicate 共享内存,retainedSlice 才需要自己释放。
  • 堆外 + 池化适合网络读写,堆内适合业务处理。
笔记加载中…