Netty中的零拷贝与我们传统理解的零拷贝不太一样。
传统的零拷贝指的是数据传输过程中,不需要CPU进行数据的拷贝,即零拷贝是不需要数据在用户空间与内核中间之间的拷贝。
一、传统的零拷贝含义
看从服务端的磁盘上读取文件,然后通过网络协议,发送给客户端的场景
DMA(Direct Memory Access)直接内存存取:主存和I/O设备之间有一条数据通路,如此一来,主存和I/O设备之间传输数据就不用中断CPU了。
这需要四次数据拷贝和四次上下文切换:
1、把服务端磁盘上的数据copy到操作系统内核的缓冲区里,这个过程是DMA(Direct Memory Access)搬运的
2、 把内核缓冲区的数据copy到用户缓冲区里,于是我们的应用程序就可以使用这部分数据了,这个copy过程是CPU完成的
3、把刚才copy到用户缓冲区里的数据,再copy到内核的socket缓冲区里,这个过程依然还是由CPU完成的。
4、把内核的socket缓冲区里的数据,copy到网卡缓冲区里,这个过程又是由DMA搬运的。
明显上面的第二步和第三步是没有必要的,通过java的FileChannel.transferTo方法,可以避免上面两次多余的拷贝(当然这需要底层操作系统支持),transferTo方法实际是实现了操作系统的sendfile。
- 调用transferTo,数据从文件由DMA引擎拷贝到操作系统内核缓冲区
- 接着DMA从操作系统内核缓冲区将数据拷贝到网卡接口buffer
上面的两次操作都不需要CPU参与,所以就达到了零拷贝。
二、netty中的零拷贝
Netty中也用到了FileChannel.transferTo方法,所以Netty的零拷贝也包括上面将的操作系统级别的零拷贝。除此之外,在ByteBuf的实现上,Netty也提供了零拷贝的一些实现。
关于ByteBuffer,Netty提供了两个接口:
1.ByteBuf
2. ByteBufHolder
对于ByteBuf,Netty提供了多种实现:
1.Heap ByteBuf: 直接在堆内存分配
2.Direct ByteBuf:直接在堆外内存分配
3.CompositeByteBuf:组合Buffer
1. Direct Buffers
直接在内存区域分配空间,而不是在堆内存中分配。如果使用传统的堆内存分配,当我们需要将数据通过socket发送的时候,就需要从堆内存拷贝到直接内存,然后再由直接内存拷贝到网卡接口层。
Netty提供的直接Buffer,直接将数据分配到内存空间,从而避免了数据的拷贝,实现了零拷贝。
2. Composite Buffers
传统的ByteBuffer,如果需要将两个ByteBuffer中的数据组合到一起,我们需要首先创建一个size=size1+size2大小的新的数组,然后将两个数组中的数据拷贝到新的数组中。但是使用Netty提供的组合ByteBuf,就可以避免这样的操作,因为CompositeByteBuf并没有真正将多个Buffer组合起来,而是保存了它们的引用,从而避免了数据的拷贝,实现了零拷贝。
3. 对于FileChannel.transferTo的使用
Netty中使用了FileChannel的transferTo方法,该方法依赖于操作系统实现零拷贝。
总结
Netty的零拷贝体现在三个方面:
-
Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要从 堆内内存 copy 到直接内存 再copy到socket,而是 直接写到堆外内存,由堆外内存(即操作系统直接内存)copy到socket。
-
Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。
-
Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。
direct buffer和heap buffer有什么区别?
1、heap buffer在堆上分配内存,有JVM负责GC,可直接想象成一个字节数组的包装类。
2、direct buffer是通过JNI在JVM外的内存中分配内存,JVM不会负责他的GC,但是当direct buffer包装类被回收时,会通过java reference机制来释放该内存块。(但Direct buffer的java对象是归GC管的,只要GC回收了他的java对象,操作系统才会释放direct buffer所申请的空间)
优劣势对比:
1、创建和释放direct buffer比heap direct代价高
2、当我们把一个direct buffer写入channel时,好比”操作系统内核缓冲区“的内容直接写入了channel,避免了内核态到用户态的数据copy。