零拷贝
零拷贝不是真的0次拷贝,而是减少拷贝次数,所有的零拷贝分为两个层面:
(1)OS级别
传统IO:
硬盘->内核缓冲区->用户缓冲区->内核缓冲区->socket缓冲区-》socket缓冲区-》网卡,共6次拷贝,四次上下文切换
mmap:
通过mmap 映射内核态内存和用户态内存,从而减少内核态与用户态的数据拷贝(一次网络数据发送:硬盘->内核态内存->socket缓冲区->网卡 ,共三次拷贝,两次上下文切换)。
sendfile
通过sendfile,可直接从内核内存拷贝到网卡发送。(一次网络数据发送:硬盘->内核态内存->网卡 ,共两次拷贝,两次上下文切换)
关于os层面的减少io操作的原理参考:https://liuhuiyao.blog.csdn.net/article/details/107743606
(2)应用级别
对各种流之间的合并,切分,封装等操作
Netty零拷贝
即所谓的 Zero-copy, 就是在操作数据时, 不需要将数据 buffer 从一个内存区域拷贝到另一个内存区域. 因为少了一次内存的拷贝, 因此 CPU 的效率就得到的提升.
在 OS 层面上的 Zero-copy 通常指避免在 用户态(User-space) 与 内核态(Kernel-space) 之间来回拷贝数据. 例如 Linux 提供的 mmap 系统调用, 它可以将一段用户空间内存映射到内核空间, 当映射成功后, 用户对这段内存区域的修改可以直接反映到内核空间; 同样地, 内核空间对这段区域的修改也直接反映用户空间. 正因为有这样的映射关系, 我们就不需要在 用户态(User-space) 与 内核态(Kernel-space) 之间拷贝数据, 提高了数据传输的效率.
而需要注意的是, Netty 中的 Zero-copy 与上面我们所提到到 OS 层面上的 Zero-copy 不太一样, Netty的 Zero-coyp完全是在用户态(Java 层面)的, 它的 Zero-copy 的更多的是偏向于优化数据操作 这样的概念.
Netty的 Zero-copy 体现方面
- Netty 提供了 CompositeByteBuf 类, 它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf, 避免了各个 ByteBuf 之间的拷贝.
- 通过 wrap 操作, 我们可以将 byte[] 数组、ByteBuf、ByteBuffer等包装成一个 Netty ByteBuf 对象, 进而避免了拷贝操作.
- ByteBuf 支持 slice 操作, 因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf, 避免了内存的拷贝.
- 通过 FileRegion 包装的FileChannel.tranferTo 实现文件传输, 可以直接将文件缓冲区的数据发送到目标 Channel, 避免了传统通过循环 write 方式导致的内存拷贝问题.
- Netty的接收和发送ByteBuffer使用直接DirectBuffer内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用JVM的堆内存进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于使用直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。