在现代操作系统(如 Linux 2.4+ 内核)和现代网卡驱动支持下,FileChannel.transferTo在数据从内核读缓冲区 到网卡缓冲区 的过程中,使用的是 DMA 拷贝 ,CPU 不参与数据搬运。
核心机制:sendfile 与 Scatter/Gather
Java 的FileChannel.transferTo底层在 Linux 上依赖的是sendfile系统调用。为了实现"零拷贝",这里经历了两个阶段的演进:
早期模式
数据从磁盘读取到内核读缓冲区是 DMA 拷贝。然后,CPU 需要将数据从内核读缓冲区拷贝到内核 Socket 缓冲区。最后,再由 DMA 从 Socket 缓冲区拷贝到网卡。
这种模式下,内核读缓冲区到 Socket 缓冲区是 CPU 拷贝。
现代模式:Linux 2.4+ 及之后
这就是现在的标准实现。硬件支持了 Scatter/Gather(分散/聚集) 操作。
- DMA 拷贝1:磁盘控制器 -> 内核读缓冲区。CPU 无参与。
- CPU 拷贝(仅元数据) :CPU 只是将内核读缓冲区的文件描述符、偏移量、长度 等信息复制到 Socket 缓冲区中。注意:此时并没有复制实际的数据内容。(CPU 开销极小)
- DMA 拷贝2:网卡驱动读取 Socket 缓冲区中的描述符,根据描述符直接去内核读缓冲区读取数据,然后发送给网卡。
总共:2 次 DMA 拷贝(数据搬运完全由 DMA 完成),0 次 CPU 数据拷贝,2 次上下文切换。
总结
在FileChannel.transferTo的实现中,数据本身从未离开过内核空间,也从未经过 CPU 的寄存器进行搬运。
- 磁盘到内核读缓冲区:DMA 拷贝。
- 内核读缓冲区到网卡:DMA 拷贝(依靠 Scatter/Gather 机制,网卡直接来"取"数据)。
这就是为什么它被称为"零拷贝"------指的是 CPU 拷贝次数为零。