【Java】Java中的零拷贝( 三 )


使用缓存I/O发送数据到网络
首先看一下使用缓存I/O从磁盘文件读取数据并发送到网络上的过程:

【Java】Java中的零拷贝

文章插图
  1. 用户发起系统调用,进入到内核态,DMA从磁盘上读取数据到内核缓冲区(DMA复制);
  2. CPU将内核缓冲区的数据拷贝到用户缓冲区(CPU复制),切换回到用户空间;
  3. 再次从用户空间切换到内核空间,CPU将用户缓冲区的数据拷贝到socket缓冲区(CPU复制);
  4. DMA将socket缓冲区的数据拷贝到网卡(DMA复制),之后从内核空间切换回用户空间;
使用缓存I/O数据经过了四次拷贝 , 需要多次在内核空间和用户空间来回切换 , 影响系统性能 。从数据拷贝的过程可以看到有些步骤其实是多余的,比如第二步,如果可以直接将内核缓存区的数据拷贝到socket缓冲区,或者直接将内核缓冲区的数据拷贝到网卡,岂不是减少了数据拷贝的次数?零拷贝就是这样一种致力于减少数据拷贝的技术 。
Linux中的零拷贝sendfileLinux在2.1版本中引入了sendfile函数,可以实现将数据从一个文件描述符传输到另外一个文件描述符:
  1. 发起sendfile系统调用,进入到内核空间;
  2. DMA从磁盘读取文件到内核缓冲区(DMA复制);
  3. 将内核缓冲区数据拷贝到socket缓冲区(CPU复制);
  4. 将socket缓冲区数据拷贝到网卡(DMA复制),之后切换回用户空间;
    【Java】Java中的零拷贝

    文章插图
sendfile减少了一次数据从内核缓冲区拷贝到用户缓冲区的过程,可以直接将内核缓冲区的数据拷贝到socket缓冲区 。
sendfile + DMA GATHERLinux在2.4版本中引入了gather技术,我们知道内核缓冲区在内存中有对应的地址,gather操作可以将内核缓冲区的内存地址、地址偏移量信息记录到socket缓冲区中,之后DMA根据地址信息从内存中读取数据到网卡中 , 减少了数据从内核缓冲区到socket缓冲区的拷贝过程:
【Java】Java中的零拷贝

文章插图
可以看到零拷贝并不是指的数据一次拷贝都没有发生,而是指减少CPU进行数据拷贝的次数 。
Java中的零拷贝MappedByteBuffer在内存映射中说过,可以通过文件映射的方式将磁盘的文件内容映射到虚拟地址空间,用户空间就可以通过虚拟地址直接访问物理内存中的映射的文件数据,而Java NIO中也提供了MappedByteBuffer来处理文件映射,使用MappedByteBuffer向网络中发送数据的过程如下:
  1. 使用MappedByteBuffer建立文件映射 , 用户空间可以通过虚拟地址直接访问映射的文件数据;
  2. 将映射的文件数据拷贝到socket网络缓冲区(CPU复制);
  3. DMA将socket缓冲区的数据拷贝到网卡(DMA复制);

【Java】Java中的零拷贝

文章插图
MappedByteBuffer减少了从内核缓冲区到用户缓冲区的数据拷贝 , 可以直接将内核缓冲区的数据拷贝到网络缓冲区 。
FileChannelJava NIO中的FileChannel可以实现将数据从FileChannel直接传输到另一个Channel,它是sendfile的一种实现:
RandomAccessFile file = new RandomAccessFile(new File("/Users/sml/test.txt"), "r");// 获取FileChannelFileChannel fileChannel = file.getChannel();long size = fileChannel.size();SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 8080));fileChannel.transferTo(0,size,socketChannel);参考
【极客时间-倪朋飞】Linux性能优化实战
【极客时间-刘超】趣谈Linux操作系统
【拉勾教育-若地】Netty 核心原理剖析与 RPC 实践
【 Kirito的技术分享】文件IO操作的最佳实践
【小码农叔叔】java使用nio读写文件
【占小狼】深入浅出MappedByteBuffer
【零壹技术栈】深入剖析Linux IO原理和几种零拷贝机制的实现
【tomas家的小拨浪鼓】堆外内存 之 DirectByteBuffer 详解
网络IO和磁盘IO详解

推荐阅读