这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战
前言
零拷贝指避免在 用户态(User-space) 与 内核态(Kernel-space) 之间来回拷贝数据,而netty的另拷贝都是基于用户态的操作,更多的是在优化数据操作上。其零拷贝主要体现在以下几个方面:
我们从netty读写源码分析一下
Netty中的零拷贝
1. 通过directe buffer实现零拷贝
Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的JVM堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才能写入Socket中。JVM堆内存的数据是不能直接写入Socket中的。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
1 | java复制代码@Override |
直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,某些情况下这部分内存也会被频繁地使用,而且也可能导致OutOfMemoryError异常出现。Java里用DirectByteBuffer可以分配一块直接内存(堆外内存),元空间对应的内存也叫作直接内存,它们对应的都是机器的物理内存。
1 | java复制代码public class MyMemoryTest { |
运行结果
从结果看出直接内存申请较慢,但访问效率高。在java虚拟机实现上,本地IO会直接操作直接内存(直接内存=>系统调用=>硬盘/网卡),而非直接内存则需要二次拷贝(堆内存=>直接内存=>系统调用=>硬盘/网卡)。
2. 通过CompositeByteBuf 实现零拷贝
CompositeByteBuf 类, 它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了各个byteBuf间的拷贝。
比如我们有两个byteBuf,header和body,现在我们想把它合成一个ByteBuf使用,通常的做法是:
1 | java复制代码ByteBuf header = Unpooled.buffer(2); |
1 | java复制代码CompositeByteBuf compositeByteBuf = Unpooled.compositeBuffer(); |
3. 通过wrap操作来实现零拷贝
比如有个byte数组,我们希望变成一个byteBuf对象, 通常的做法
1 | java复制代码byte[] b = new byte[10]; |
使用wrap操作则可以避免这次额外的拷贝
1 | java复制代码byteBuf = Unpooled.wrappedBuffer(b); |
3. 通过slice的方式实现零拷贝
slice 操作和 wrap 操作刚好相反, Unpooled.wrappedBuffer
可以将多个 ByteBuf 合并为一个, 而 slice 操作可以将一个 ByteBuf 切片
为多个共享一个存储区域的 ByteBuf 对象.
ByteBuf 提供了两个 slice 操作方法:
1 | ini复制代码ByteBuf byteBuf = ... |
用 slice
方法产生 header 和 body 的过程是没有拷贝操作的, header 和 body 对象在内部其实是共享了 byteBuf 存储空间的不同部分而已. 即:
5. 通过 FileRegion 实现零拷贝
Netty 中使用 FileRegion 实现文件传输的零拷贝, 不过在底层 FileRegion 是依赖于 Java NIO FileChannel.transfer
的零拷贝功能.
下面是传统的实现文件拷贝的功能,
1 | java复制代码public static void copyFile(String srcFile, String destFile) throws Exception { |
从源文件中读取定长数据到 temp 数组中, 然后再将 temp 中的内容写入目的文件, 这样的拷贝操作对于小文件倒是没有太大的影响, 但是如果我们需要拷贝大文件时, 频繁的内存拷贝操作就消耗大量的系统资源了.
用 Java NIO 的 FileChannel
实现零拷贝。
1 | java复制代码public static void copyFileWithFileChannel(String srcFileName, String destFileName) throws Exception { |
可以看到, 使用了 FileChannel
后, 我们就可以直接将源文件的内容直接拷贝(transferTo
) 到目的文件中, 而不需要额外借助一个临时 buffer, 避免了不必要的内存操作.
我们来看一下在 Netty 中是怎么使用 FileRegion
来实现零拷贝传输一个文件的:
样例代码我们直接用netty中的代码,FileServerHandler
1 | java复制代码@Override |
本文转载自: 掘金