Netty的“零拷贝”指避免CPU主动拷贝,通过堆外内存(DirectByteBuf)绕过JVM堆与Socket间复制,CompositeByteBuf则通过组件列表引用管理多缓冲区而不合并内存;二者协同实现writeAndFlush全程无用户态搬运。

Netty 的“零拷贝”不是不拷贝,而是不让你的代码主动触发 CPU 拷贝——尤其是跨用户态/内核态、或在多个缓冲区之间反复搬数据。 堆外内存(DirectByteBuf)和 CompositeByteBuf 的组合,正是这个理念落地最典型的双引擎:一个管“数据不出内核”,一个管“数据不合并挪动”。
为什么 CompositeByteBuf 不等于内存复制?
它根本不分配新内存,只是维护一个 Component 列表,每个 Component 记录着子 ByteBuf 的引用、起始偏移、长度。读 getByte(150) 时,它查表知道第 150 字节落在哪个子缓冲区、再转成那个子缓冲区内部的相对索引去读——全程无 memcpy。
- 常见错误现象:
CompositeByteBuf.array()抛UnsupportedOperationException,因为它压根没有 backing array - 使用场景:HTTP 响应头(
Unpooled.copiedBuffer)+ 响应体(PooledDirectByteBuf)拼一起发,不 copy - 参数差异:
compositeBuffer().addComponent(true, buf1).addComponent(true, buf2)中第一个true表示自动 retain,避免被提前释放 - 性能影响:添加 100 个子
ByteBuf,只要不频繁随机访问跨组件边界,性能几乎线性;但slice()或copy()会触发现实拷贝,破功
堆外内存 + CompositeByteBuf 的真实协作链路
堆外内存本身解决的是 JVM 堆与 Socket 之间的零拷贝(绕过 GC 和堆复制),而 CompositeByteBuf 解决的是“多个堆外块怎么当一个用”。两者叠加,才能让一次 writeAndFlush 完全避开用户态内存搬运。
- 常见错误现象:混合用了
Unpooled.heapBuffer()和PooledDirectByteBuf到同一个CompositeByteBuf,结果write时触发隐式copy()—— 因为某些 Channel(如EpollSocketChannel)只接受 direct buffer - 使用场景:文件传输中,用
FileRegion发送大文件(走transferTo),同时在前面 prepend 一个自定义协议头(Unpooled.directBuffer()),用CompositeByteBuf包裹后统一 write - 兼容性影响:JDK 8u292+ 和 Linux kernel 2.4+ 才能真正发挥
transferTo+ SG-DMA 的终极零拷贝;老环境 fallback 到sendfile,仍比传统 IO 少一次 CPU 拷贝
release 的坑:谁 retain 谁 release,CompositeByteBuf 不背锅
CompositeByteBuf 自身是 ReferenceCounted,但它默认不接管子 ByteBuf 的生命周期——除非你显式传 true 给 addComponent。这意味着:子缓冲区该谁 release 还是得谁来。
- 常见错误现象:子
ByteBuf在CompositeByteBuf写出前就被别的 handlerrelease()了,导致 write 时抛IllegalReferenceCountException - 实操建议:
addComponent(false, header)→ 头部由上游 handler 管;addComponent(true, body)→ 正文由 composite 自己负责释放 - 关键点:
CompositeByteBuf.release()只会释放自己持有的引用计数,若子 buf 是true添加的,才会顺带调child.release();否则子 buf 的生命周期完全独立
真正难的从来不是“怎么写出来”,而是判断哪段数据该用 direct、哪段该 pooled、哪个时机该 retain、哪个 handler 该放手——这些决策一旦错位,零拷贝就退化成“零文档说明的拷贝”。








