面试被问到“零拷贝”!你真的理解吗?

前言java

从字面意思理解就是数据不须要来回的拷贝,大大提高了系统的性能;这个词咱们也常常在java nio,netty,kafka,RocketMQ等框架中听到,常常做为其提高性能的一大亮点;下面从I/O的几个概念开始,进而在分析零拷贝。面试

I/O概念网络

一、缓冲区app

缓冲区是全部I/O的基础,I/O讲的无非就是把数据移进或移出缓冲区;进程执行I/O操做,就是向操做系统发出请求,让它要么把缓冲区的数据排干(写),要么填充缓冲区(读);下面看一个java进程发起read请求加载数据大体的流程图:框架

面试被问到“零拷贝”!你真的理解吗?

进程发起read请求以后,内核接收到read请求以后,会先检查内核空间中是否已经存在进程所须要的数据,若是已经存在,则直接把数据copy给进程的缓冲区;若是没有内核随即向磁盘控制器发出命令,要求从磁盘读取数据,磁盘控制器把数据直接写入内核read缓冲区,这一步经过DMA完成;接下来就是内核将数据copy到进程的缓冲区;jvm

若是进程发起write请求,一样须要把用户缓冲区里面的数据copy到内核的socket缓冲区里面,而后再经过DMA把数据copy到网卡中,发送出去;socket

你可能以为这样挺浪费空间的,每次都须要把内核空间的数据拷贝到用户空间中,因此零拷贝的出现就是为了解决这种问题的;分布式

关于零拷贝提供了两种方式分别是:mmap+write方式,sendfile方式;微服务

二、虚拟内存源码分析

全部现代操做系统都使用虚拟内存,使用虚拟的地址取代物理地址,这样作的好处是:

1)一个以上的虚拟地址能够指向同一个物理内存地址,

2)虚拟内存空间可大于实际可用的物理地址;

利用第一条特性能够把内核空间地址和用户空间的虚拟地址映射到同一个物理地址,这样DMA就能够填充对内核和用户空间进程同时可见的缓冲区了,大体以下图所示:

面试被问到“零拷贝”!你真的理解吗?

省去了内核与用户空间的往来拷贝,java也利用操做系统的此特性来提高性能,下面重点看看java对零拷贝都有哪些支持。

三、mmap+write方式

使用mmap+write方式代替原来的read+write方式,mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系;这样就能够省掉原来内核read缓冲区copy数据到用户缓冲区,可是仍是须要内核read缓冲区将数据copy到内核socket缓冲区,大体以下图所示:

面试被问到“零拷贝”!你真的理解吗?

四、sendfile方式

sendfile系统调用在内核版本2.1中被引入,目的是简化经过网络在两个通道之间进行的数据传输过程。sendfile系统调用的引入,不只减小了数据复制,还减小了上下文切换的次数,大体以下图所示:

面试被问到“零拷贝”!你真的理解吗?

数据传送只发生在内核空间,因此减小了一次上下文切换;可是仍是存在一次copy,能不能把这一次copy也省略掉,Linux2.4内核中作了改进,将Kernel buffer中对应的数据描述信息(内存地址,偏移量)记录到相应的socket缓冲区当中,这样连内核空间中的一次cpu copy也省掉了;

Java零拷贝

一、MappedByteBuffer

java 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;

二、DirectByteBuffer

DirectByteBuffer继承于MappedByteBuffer,从名字就能够猜想出开辟了一段直接的内存,并不会占用jvm的内存空间;上一节中经过Filechannel映射出的MappedByteBuffer其实际也是DirectByteBuffer,固然除了这种方式,也能够手动开辟一段空间:

面试被问到“零拷贝”!你真的理解吗?

如上开辟了100字节的直接内存空间;

三、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里面对象的几率来理解的话,其实就是使用的都是对象的引用,每一个引用对象的地方对其改变就都能改变此对象,永远只存在一份对象。

若是想学习Java工程化、高性能及分布式、深刻浅出。微服务、Spring,MyBatis,Netty源码分析的朋友能够加个人Java高级交流:787707172,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给你们。

相关文章
相关标签/搜索