9.Netty的零拷贝
- Netty 的零拷贝分两类:OS 级别(FileRegion)和用户态级别(DirectBuffer、CompositeByteBuf、wrappedBuffer、slice)
- 用户态零拷贝的核心思路:逻辑合并/切分,底层共享同一块内存,不做实际数据搬运
- OS 级别零拷贝原理见 Linux中的零拷贝技术
1. 为什么需要用户态零拷贝?
OS 级别的零拷贝(sendfile)解决的是内核态的数据搬运问题,但 JVM 内部还有一层:
JVM 堆内存 → CPU 拷贝 → 堆外内存 → 系统调用 → 内核
执行 I/O 系统调用时,OS 不认识 JVM 堆内存(GC 随时可能移动对象),必须先把数据拷贝到堆外内存才能调用。Netty 在用户态做了 5 处优化来减少这类拷贝。
2. 堆外内存(DirectBuffer)
Netty 的 I/O 读写全部使用堆外内存(DirectBuffer),数据直接在堆外操作,省掉了堆内 → 堆外这一次 CPU 拷贝。
代价是堆外内存不受 GC 管理,需要手动释放,Netty 用引用计数(ReferenceCounted)来管理生命周期。
3. CompositeByteBuf(逻辑合并)
需要把 header 和 body 合并成一个完整报文时,传统做法要分配新内存再拷贝两次:
// 传统做法:2 次 CPU 拷贝
ByteBuf httpBuf = Unpooled.buffer(header.readableBytes() + body.readableBytes());
httpBuf.writeBytes(header); // 拷贝 1
httpBuf.writeBytes(body); // 拷贝 2
CompositeByteBuf 只是把多个 ByteBuf 的引用组合在一起,底层 byte 数组不动:
// 零拷贝:不发生内存拷贝
CompositeByteBuf httpBuf = Unpooled.compositeBuffer();
httpBuf.addComponents(true, header, body);

内部每个 Component 记录的是偏移量,读取时按偏移量定位到对应的原始 ByteBuf:
private static final class Component {
final ByteBuf srcBuf; // 原始 ByteBuf
final ByteBuf buf; // 去除包装后的 ByteBuf
int offset; // 相对于 CompositeByteBuf 的起始位置
int endOffset; // 相对于 CompositeByteBuf 的结束位置
int srcAdjustment; // CompositeByteBuf 起始索引相对于 srcBuf 读索引的偏移
int adjustment; // CompositeByteBuf 起始索引相对于 buf 读索引的偏移
}

读取过程中各 Component 的偏移量变化示意(header 读 1 字节,body 读 2 字节):

4. Unpooled.wrappedBuffer(包装,不拷贝)
把已有的 byte[]、ByteBuf、ByteBuffer 包装成 ByteBuf,不产生内存拷贝:
byte[] bytes = ...;
ByteBuf buf = Unpooled.wrappedBuffer(bytes); // 零拷贝包装
也是创建 CompositeByteBuf 的另一种推荐方式:
ByteBuf composite = Unpooled.wrappedBuffer(header, body);

5. ByteBuf.slice(切分,不拷贝)
和 wrappedBuffer 方向相反,把一个 ByteBuf 切成多个视图,底层共享同一块内存:
ByteBuf httpBuf = ...;
ByteBuf header = httpBuf.slice(0, 6); // 前 6 字节,零拷贝
ByteBuf body = httpBuf.slice(6, 4); // 后 4 字节,零拷贝

注意:slice 出来的 ByteBuf 和原始 ByteBuf 共享内存,修改其中一个会影响另一个。
6. FileRegion(OS 级零拷贝)
文件传输场景,FileRegion 封装了 FileChannel.transferTo(),底层走 Linux 的 sendfile,数据直接从内核缓冲区传到网卡,不经过用户态:
FileRegion region = new DefaultFileRegion(fileChannel, 0, fileLength);
ctx.writeAndFlush(region);
OS 级零拷贝的原理见 [[Linux中的零拷贝技术]]。
7. 汇总对比
| 技术 | 类型 | 解决的问题 | 原理 |
|---|---|---|---|
| DirectBuffer | 用户态 | 堆内 → 堆外拷贝 | I/O 直接用堆外内存 |
| CompositeByteBuf | 用户态 | 多 buffer 合并拷贝 | 逻辑合并,共享底层数组 |
| wrappedBuffer | 用户态 | byte[] 转 ByteBuf 拷贝 | 包装引用,不复制数据 |
| slice | 用户态 | ByteBuf 切分拷贝 | 共享底层数组,偏移量定位 |
| FileRegion | OS 级 | 内核态文件传输拷贝 | sendfile,数据不经过用户态 |