网络文件传输之零拷贝

5 篇文章 0 订阅
3 篇文章 0 订阅

一、传统文件传输

        传统文件传输的操作流程如下所示:

  1. 首先调用Java中的file.read方法,由于Java的IO流并不具备IO读写能力,所以此时会先从用户态切换至内核态(Kernel),利用内核的能力去读取磁盘中的文件并将数据存放在内核缓冲区中,在这个期间用户线程会阻塞住。(内核使用DMA(Direct Memory Access 直接内存访问)来进行文件读,期间不会使用CPU);
  2. 从内核态切换为用户态,将数据从内核缓冲区读取至用户缓冲区(buffer),期间CPU参与拷贝;
  3. 调用file.write方法,将数据从用户缓冲区写至socket缓冲区,CPU参与拷贝;
  4. 此时需要从socket缓冲区向网卡写数据,用户态不具备该能力,所以需要重新切换至内核态,调用内核的写能力,使用DMA将数据从socket缓冲区写至网卡,不需要CPU,文件拷贝完成。

        可以看到上面文件的读写表层虽然是利用了Java的API,但Java的API实际不是物理设备级别的读写,只是缓存层次的复制,底层的读写其实是内核来完成的,在此期间,文件数据被复制了4次,用户态与内核态之间切换了三次(重量级操作),都很消耗性能。

二、优化V1.0

        NIO优化后的流程如下:

        NIO通过directByteBuffer来优化文件传输,directByteBuffer由ByteBuffer.allocateDirect()分配得到,是一块操作系统内存和jvm内存映射的内存空间,相当于jvm能够直接操作操作系统内存,也就少了在读取数据时由内核缓冲区拷贝至用户缓冲区的过程,因为现在这两块内存实际上是相互映射的同一块内存,并且这块内存不受JVM垃圾回收的影响,内存地址固定,有利于IO读写。

        Java中的directByteBuffer对象仅仅维护了此内存对象的虚引用,内存回收分为两步:

  1. directByteBuffer对象被回收,虚引用加入到引用队列;
  2. 通过专用线程访问引用队列,释放虚引用对应的堆外内存。

三、优化V2.0

         在2.0版本,NIO应用Linux2.1后提供的sendFile方法做了进一步优化,Java中体现在两个channel调用transferTo/transferFrom方法上:

         相较原版传输,Java首先调用transferTo方法,此时用户态切换为内核态,使用DMA将文件数据读入内核缓冲区,不使用CPU;然后利用CPU将数据由内核缓冲区拷贝至socket缓冲区;最终使用DMA将数据由socket缓冲区拷贝至网卡。整个传输过程中只发生了一次用户态到内核态的切换,且文件只拷贝了三次。

 四、优化V3.0

        在linux2.4之后对transferTo/transferFrom方法底层做了进一步优化,如图:

       相较原版传输:Java首先调用transferTo方法,此时用户态切换为内核态,使用DMA将文件数据读入内核缓冲区,不使用CPU;然后利用CPU将一些offset和length信息拷贝至socket缓存区,几乎无消耗;最终使用DMA将数据从内核缓冲区拷贝至网卡。整个传输过程中只发生了一次用户态到内核态的切换,数据只拷贝了两次,也就是所谓的零拷贝(注意零拷贝其实并不是真正的不拷贝,而是只将数据很小的offset和length信息进行拷贝,不会拷贝重复数据在jvm内存中)。

零拷贝优点:内核态与用户态之间切换次数少;不利用CPU计算和拷贝文件,减少CPU缓存伪共享;适合小文件传输。

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值