!
做者:ksfzhaohui
媒介从字面意思理解就是数据不需要来回的拷贝,大大提拔了系统的性能;那个词我们也经常在java nio,netty,kafka,RocketMQ等框架中听到,经常做为其提拔性能的一大亮点;下面从I/O的几个概念起头,进而在阐发零拷贝。I/O概念1、缓冲区缓冲区是所有I/O的根底,I/O讲的无非就是把数据移进或移出缓冲区;历程施行I/O操做,就是向操做系统发出恳求,让它要么把缓冲区的数据排干(写),要么填充缓冲区(读);下面看一个java历程倡议read恳求加载数据大致的流程图:
历程倡议read恳求之后,内核领受到read恳求之后,会先查抄内核空间中能否已经存在历程所需要的数据,若是已经存在,则间接把数据copy给历程的缓冲区;若是没有内核随即向磁盘控造器发出号令,要求从磁盘读取数据,磁盘控造器把数据间接写入内核read缓冲区,那一步通过DMA完成;接下来就是内核将数据copy到历程的缓冲区;若是历程倡议write恳求,同样需要把用户缓冲区里面的数据copy到内核的socket缓冲区里面,然后再通过DMA把数据copy到网卡中,发送进来;你可能觉得如许挺浪费空间的,每次都需要把内核空间的数据拷贝到用户空间中,所以零拷贝的呈现就是为领会决那种问题的;关于零拷贝供给了两种体例别离是:mmap+write体例,sendfile体例;
2、虚拟内存所有现代操做系统都利用虚拟内存,利用虚拟的地址代替物理地址,如许做的益处是:1)一个以上的虚拟地址能够指向统一个物理内存地址,2)虚拟内存空间可大于现实可用的物理地址;操纵第一条特征能够把内核空间地址和用户空间的虚拟地址映射到统一个物理地址,如许DMA就能够填充对内核和用户空间历程同时可见的缓冲区了,大致如下图所示:
省去了内核与用户空间的往来拷贝,java也操纵操做系统的此特征来提拔性能,下面重点看看java对零拷贝都有哪些撑持。
3、mmap+write体例利用mmap+write体例取代本来的read+write体例,mmap是一种内存映射文件的办法,即将一个文件或者其它对象映射到历程的地址空间,实现文件磁盘地址和历程虚拟地址空间中一段虚拟地址的逐个对映关系;如许就能够免却本来内核read缓冲区copy数据到用户缓冲区,但是仍是需要内核read缓冲区将数据copy到内核socket缓冲区,大致如下图所示:
4、sendfile体例sendfile系统挪用在内核版本2.1中被引入,目标是简化通过收集在两个通道之间停止的数据传输过程。sendfile系统挪用的引入,不只削减了数据复造,还削减了上下文切换的次数,大致如下图所示:
数据传送只发作在内核空间,所以削减了一次上下文切换;但是仍是存在一次copy,能不克不及把那一次copy也省略掉,Linux2.4内核中做了改良,将Kernel buffer中对应的数据描述信息(内存地址,偏移量)记录到响应的socket缓冲区傍边,如许连内核空间中的一次cpu copy也免却了;
Java零拷贝1、MappedByteBufferjava nio供给的FileChannel供给了map()办法,该办法能够在一个翻开的文件和MappedByteBuffer之间成立一个虚拟内存映射,MappedByteBuffer继承于ByteBuffer,类似于一个基于内存的缓冲区,只不外该对象的数据元素存储在磁盘的一个文件中;挪用get()办法会从磁盘中获取数据,此数据反映该文件当前的内容,挪用put()办法会更新磁盘上的文件,而且对文件做的修改对其他阅读者也是可见的;下面看一个简单的读取实例,然后在对MappedByteBuffer停止阐发:
次要通过FileChannel供给的map()来实现映射,map()办法如下:
别离供给了三个参数,MapMode,Position和size;别离暗示:MapMode:映射的形式,可选项包罗:READ_ONLY,READ_WRITE,PRIVATE;Position:从哪个位置起头映射,字节数的位置;Size:从position起头向后几个字节;
重点看一下MapMode,请两个别离暗示只读和可读可写,当然恳求的映射形式遭到Filechannel对象的拜候权限限造,若是在一个没有读权限的文件上启用READ_ONLY,将抛出NonReadableChannelException;PRIVATE形式暗示写时拷贝的映射,意味着通过put()办法所做的任何修改城市招致产生一个私有的数据拷贝而且该拷贝中的数据只要MappedByteBuffer实例能够看到;该过程不会对底层文件做任何修改,并且一旦缓冲区被施以垃圾搜集动做(garbage collected),那些修改城市丧失;大致阅读一下map()办法的源码:
大请安思就是通过native办法获取内存映射的地址,若是失败,手动gc再次映射;最初通过内存映射的地址实例化出MappedByteBuffer,MappedByteBuffer自己是一个笼统类,其实那里实正实例话出来的是DirectByteBuffer;
2、DirectByteBufferDirectByteBuffer继承于MappedByteBuffer,从名字就能够推测出开拓了一段间接的内存,其实不会占用jvm的内存空间;上一节中通过Filechannel映射出的MappedByteBuffer其现实也是DirectByteBuffer,当然除了那种体例,也能够手动开拓一段空间:
如上开拓了100字节的间接内存空间;
3、Channel-to-Channel传输经常需要从一个位置将文件传输到别的一个位置,FileChannel供给了transferTo()办法用来进步传输的效率,起首看一个简单的实例:
通过FileChannel的transferTo()办法将文件数据传输到System.out通道,接口定义如下:
几个参数也比力好理解,别离是起头传输的位置,传输的字节数,以及目的通道;transferTo()允许将一个通道穿插毗连到另一个通道,而不需要一个中间缓冲区来传递数据;注:那里不需要中间缓冲区有两层意思:第一层不需要用户空间缓冲区来拷贝内核缓冲区,别的一层两个通道都有本身的内核缓冲区,两个内核缓冲区也能够做到无需拷贝数据;
Netty零拷贝netty供给了零拷贝的buffer,在传输数据时,最末处置的数据会需要对单个传输的报文,停止组合和拆分,Nio原生的ByteBuffer无法做到,netty通过供给的Composite(组合)和Slice(拆分)两种buffer来实现零拷贝;看下面一张图会比力明晰:TCP层HTTP报文被分红了两个ChannelBuffer,那两个Buffer对我们上层的逻辑(HTTP处置)是没有意义的。但是两个ChannelBuffer被组合起来,就成为了一个有意义的HTTP报文,那个报文对应的ChannelBuffer,才是能称之为”Message”的工具,那里用到了一个词”Virtual Buffer”。能够看一下netty供给的CompositeChannelBuffer源码:
components用来保留的就是所有领受到的buffer,indices记录每个buffer的起始位置,lastAccessedComponentId记录上一次拜候的ComponentId;CompositeChannelBuffer其实不会开拓新的内存并间接复造所有ChannelBuffer内容,而是间接保留了所有ChannelBuffer的引用,并在子ChannelBuffer里停止读写,实现了零拷贝。
其他零拷贝RocketMQ的动静接纳挨次写到commitlog文件,然后操纵consume queue文件做为索引;RocketMQ接纳零拷贝mmap+write的体例来回应Consumer的恳求;同样kafka中存在大量的收集数据耐久化到磁盘和磁盘文件通过收集发送的过程,kafka利用了sendfile零拷贝体例;
总结零拷贝若是简单用java里面临象的概率来理解的话,其实就是利用的都是对象的引用,每个引用对象的处所对其改动就都能改动此对象,永久只存在一份对象。














还没有评论,来说两句吧...