关于零拷贝和堆外内存
Java在将数据发送出去的时候,会先将数据从堆内存拷贝到堆外内存,然后才会将堆外内存再拷贝到内核态,进行消息的收发,代码如下:
所以,我们发现,假如我们在收发报文的时候使用直接内存,那么就可以减少一次内存拷贝,Netty就是这么做的。
Netty在通信层进行字节流的接收和发送的时候,如果应用允许Unsafe访问,则会采用DirectBvteBuf进行转换也就是堆外的直接内存,代码如下:
CompositeByteBuf
考虑一种场景,当一个数据包被拆成了两个字节流通过TCP传输过来后,那么对于接收者的机器来说,为了方便解析,它需要新建一个BvteBuf将这两个字节流重组成一个新的数据包,如下图所示:
那么在这种情况下,我们如果直接将两个字节流拷贝到一个新的字节流中,显然会浪费空间和时间,所以Netty推出了CompositeByteBuf,专门用来拷贝ByteBuf
Unpooled.wrappedBuffer
Unpooled.wrappedBuffer 是创建 CompositeByteBuf 对象的另一种推荐做法。
Unpooled.wrappedBuffer 方法可以将不同的数据源的一个或者多个数据包装成一个大的 BvteBuf 对象,其中数据源的类型包括 bytel、ByteBuf、ByteBufer。包装的过程中不会发生数据拷贝操作,包装后生成的 ByteBuf 对象和原始 ByteBuf 对象是共享底层的 byte 数组,
ByteBuf.slice
ByteBuf.slice 和 Unpooled.wrappedBuffer 的逻辑正好相反,ByteBuf.sice 是将一个 ByteBuf对象切分成多个共享同一个底层存储的 BvteBuf 对象。
FileRegion文件传输
Netty 使用 FileRegion 实现文件传输的零拷贝,而FileRegion其实是基于Java底层的FileChannel#tranferTo方法实现的。它可以根据操作系统直接将文件缓冲区的数据发送到目标channel,底层借助了sendFile能力避免了传统通过循环write方式导致的内存拷贝问题。所以 FileRegion 是操作系统级别的零拷贝。
sendFile
JDK原生的FileChannel#tranferTo方法其实是基于了Linux的sendFile方法,通过该方法,数据可以直接在内核空间内部进行 I/O 传输,从而省去了数据在用户空间和内核空间之间的来回拷贝。工作原理如下图: