磁盘只能操作固定大小的数据块,而用户进程可能需要任意大小的、非对齐的数据块,内核负责了数据的分解、组合工作,充当着中间人的角色。

这里说明一下:在程序不需要修改数据,程序会尝试发起 sendfile() 系统调用取代 read(),即文件数据不用从 kernel buffer 拷贝到 user space,而是直接从磁盘文件拷贝到 kernel buffer,之后 kernel buffer 中的数据被拷贝到 send buffer,再通过 DMA 的方式拷贝到网卡中发给接收端。而在程序需要更改数据的情况下,JVM 会尝试 mmap() 系统调用(也就是说上图是“数据需要修改”的情况),将文件映射到内存中,直接操作映射到内核空间内存上的文件(OS 提供文件的写最终一致),依然不用拷贝到 user space,这也减少了 2 次 IO。

关于尝试使用零拷贝技术,可以参考 Netty 的 Javadoc,如 FileChannel#transfer。