Java零拷贝应该怎么用
零拷贝(Zero-Copy)在 Java 里主要是通过 NIO(New I/O) 提供的 API 来实现的,它能减少内核态和用户态之间的数据拷贝次数,从而提升性能。常见场景是网络传输、大文件读写、消息队列(Kafka、RocketMQ)、高性能服务器(Netty)等。
一、传统 IO 的问题
- 传统 IO(
InputStream/OutputStream)读文件时,会先从磁盘拷贝到内核缓冲区,再从内核缓冲区拷贝到用户空间的 Java 堆,然后应用再写入 Socket 时,又要从用户空间拷贝到内核 Socket 缓冲区,最后再从内核 Socket 缓冲区拷贝到网卡。 - 总共有 4 次拷贝 + 2 次上下文切换。
二、Java 零拷贝的实现方式
1. FileChannel.transferTo / transferFrom
这是最典型的零拷贝方式。
示例:把一个文件直接写到 SocketChannel,中间不经过 Java 堆。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
public class ZeroCopyDemo {
public static void main(String[] args) throws Exception {
// 客户端连接服务器
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 8080));
// 打开文件
FileChannel fileChannel = new FileInputStream("bigfile.data").getChannel();
// 零拷贝传输
long position = 0;
long size = fileChannel.size();
while (position < size) {
long transferred = fileChannel.transferTo(position, size - position, socketChannel);
position += transferred;
}
fileChannel.close();
socketChannel.close();
}
}
📌 transferTo / transferFrom 底层会调用 Linux 的 sendfile(),这就是零拷贝的核心。
在 Linux 内核 2.4 之后,只需 2 次拷贝:
- 磁盘 → 内核缓冲区(DMA 拷贝)
- 内核缓冲区 → 网卡(DMA 拷贝)
应用层 不需要参与数据搬运。
2. MappedByteBuffer(内存映射)
通过 FileChannel.map() 把文件直接映射到内存,访问时直接读写虚拟内存。
示例:
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MmapDemo {
public static void main(String[] args) throws Exception {
RandomAccessFile raf = new RandomAccessFile("test.txt", "rw");
FileChannel channel = raf.getChannel();
// 映射文件到内存 (读写模式)
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size());
// 直接修改内存,相当于修改文件
buffer.put(0, (byte) 'H');
buffer.put(1, (byte) 'i');
channel.close();
raf.close();
}
}
📌 使用 mmap,可以避免系统调用和拷贝,但要注意:
- 适合随机读写,而非一次性顺序传输。
- 文件过大时,容易触发
OutOfMemoryError(因为内存映射会占用虚拟内存空间)。
3. DirectByteBuffer(堆外内存)
Java NIO 提供 ByteBuffer.allocateDirect(),分配的内存不在 JVM 堆里,而是操作系统直接分配的堆外内存。
好处:避免了 JVM 堆 ↔ 内核缓冲区 的一次拷贝。
缺点:需要手动回收(GC 不会立刻回收)。
import java.nio.ByteBuffer;
public class DirectBufferDemo {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024); // 堆外内存
buffer.put((byte) 1);
buffer.flip();
System.out.println(buffer.get());
}
}
Netty 就是基于 DirectByteBuffer 做高性能网络传输的。
三、应用场景
- 高性能文件传输 → 使用
FileChannel.transferTo()(Kafka 依赖它)。 - 内存映射大文件 → 使用
MappedByteBuffer(常见于索引文件、日志存储)。 - 高性能网络通信框架 → 使用
DirectByteBuffer(Netty、gRPC)。
四、总结
- 文件到网络 → 用
transferTo/transferFrom(零拷贝 sendfile)。 - 随机读写文件 → 用
MappedByteBuffer(mmap)。 - 高性能网络通信 → 用
DirectByteBuffer(堆外内存)。

浙公网安备 33010602011771号