前言字面意思就是数据不需要来回拷贝,大大提高了系统的性能;这个词在javanio、netty、kafka、RocketMQ等框架中经常听到,经常作为其性能提升的亮点;下面从I/O的几个概念入手,然后分析零拷贝。I/O概念1.BufferBuffer是所有I/O的基础。I/O只不过是将数据移入或移出缓冲区;当一个进程执行一个I/O操作时,它向操作系统发送一个请求,让它要么清空缓冲区中的数据(写),要么填充缓冲区(读);我们看一个java进程发起读请求加载数据的大致流程:进程发起读请求后,内核收到读请求后,首先会检查内核中是否已经存在该进程需要的数据space,如果已经存在,则直接将数据复制到进程的buffer中;如果没有内核,则向磁盘控制器发送命令从磁盘读取数据,磁盘控制器将数据直接写入内核读缓冲区,这一步由DMA完成;下一步是内核将数据复制到进程缓冲区;如果进程发起写请求,还需要将userbuffer中的数据复制到内核socketbuffer中,然后通过DMA将数据复制到网卡中,并发送出去;你可能会觉得这样很浪费空间,每次都需要把内核空间的数据拷贝到用户空间,所以零拷贝的出现就是为了解决这个问题。问题;提供零拷贝的方式有两种:mmap+write方式,sendfile方式;2.虚拟内存所有现代操作系统都使用虚拟内存,使用虚拟地址代替物理地址。这样做的好处是:1。多个虚拟地址可以指向同一个物理内存地址,2.虚拟内存空间可以大于实际可用的物理地址;利用第一个特性,可以将内核空间地址和用户空间虚拟地址映射到同一个物理地址,这样DMA就可以填充对内核和用户空间进程都可见的buffer,大致如下图图:省略了内核空间和用户空间之间的拷贝,java也利用了操作系统的这个特性来提高性能。下面重点看看java对零拷贝有什么支持。3、mmap+write方式使用mmap+write方式代替原来的read+write方式。mmap是一种内存映射文件方法,将文件或其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址。空间中虚拟地址的一一映射关系;这样,原来内核读缓冲区拷贝数据到用户缓冲区就可以省了,但是内核读缓冲区还是需要把数据拷贝到内核socket缓冲区,大致如下图:4.sendfile方式的sendfile系统调用是在2.1内核版本中引入的,用于简化网络上两个通道之间的数据传输过程。sendfile系统调用的引入,不仅减少了数据的拷贝,也减少了上下文切换的次数,如下图所示:数据传输只发生在内核空间,因此减少了一次上下文切换;但是还有一个copy,能不能把这个copy也省略了,linux2.4内核做了改进,将Kernelbuffer中对应的数据描述信息(内存地址,偏移量)记录到对应的socketbuffer中,这样一个CPU连接在内核空间的拷贝也被省略了;Java零拷贝1.MappedByteBufferjavanio提供的FileChannel提供了map()方法,可以在打开的文件和MappedByteBuffer之间建立虚拟内存映射。MappedByteBuffer继承自ByteBuffer,类似于一个内存缓冲区,只不过对象的数据元素存储在磁盘上的一个文件中;调用get()方法会从磁盘中获取数据,这个数据反映了文件当前的内容,调用put()方法会更新磁盘文件上的数据,对文件所做的修改也是对其他读者可见;我们看一个简单的读取例子,然后分析MappedByteBuffer:长len=file.length();byte[]ds=newbyte[(int)len];MappedByteBuffermappedByteBuffer=newFileInputStream(file).getChannel().map(FileChannel.MapMode.READ_ONLY,0,len);for(intoffset=0;offset
