
首先,原谅我做了标题党,但是内容还是值得一看的。
不少关于Netty的文章和视频都表达一个意思,Netty之所以牛逼其中一个原因就是因为它的零拷贝机制。
那么这个零拷贝到底是什么?根据我查阅资料,发现Netty的零拷贝体现在下面3个核心技术点,我们一起来看看真相吧!
可以和Netty零拷贝,扯上关系的第一个零拷贝就是Linux的零拷贝。我们用一个典型的文件传输案例来聊聊!

这个典型的案例中,经过了如下2步
这样没有问题,但是我们如果让这个数据不通过用户空间,直接在内核态进行数据填充了,效果是不是更好,看下图?

上面这种数据不经过用户空间,直接在内核空间填充的技术,就是零拷贝。
Linux是提供了这类技术的,具体实现也有很多,这不是本文的重点,有兴趣的请自行谷歌。
Java的NIO提供了一个API叫做transferTo,而Netty也就在FileRegion中简单的包裹了一下,具体可以参考该类io.netty.channel.DefaultFileRegion#transferTo
综上:关于文件的零拷贝,这不是Netty原创的零拷贝,还是要归功于NIO的封装,当然最终还是要归功于系统!
第二个Netty零拷贝相关的就是避C-Heap和Java-Heap的转换。
我们需要知道Java读取文件流或者网络流,其底层都是native的实现,就是说Java调用了C来实现的。
比如java.net.SocketInputStream#socketRead0
private native int socketRead0(FileDescriptor fd,
byte b[], int off, int len,
int timeout)
throws IOException;
比如java.io.FileInputStream#read0
private native int read0() throws IOException;
这种API调用方式,势必会导致C-Heap 到Java-Heap的一层拷贝,这个是会耗费资源的。
NIO本身就实现在堆外的内存DirectByteBuffer,Netty的ByteBuf也就是在这个上面封装了一层。我们来看看Netty如何应用NIO的DirectByteBuffer来避免这一层C-Java的拷贝的。
内存池的ByteBuf创建的调用链路
io.netty.buffer.PooledByteBufAllocator#newDirectBuffer
io.netty.buffer.UnsafeByteBufUtil#newUnsafeDirectByteBuf
io.netty.buffer.UnpooledDirectByteBuf#UnpooledDirectByteBuf
io.netty.buffer.UnpooledDirectByteBuf#allocateDirect
java.nio.DirectByteBuffer#DirectByteBuffer(int)
综上:关于ByteBuffer的零拷贝,不是Netty原创还是要归功于NIO
这个才算是Netty在零拷贝上的真实体现,原创精品,哈哈!
Composite是复合的意思,可以看出是复合ByteBuf,这是一个NIO的ByteBuffer的包装器。可以将实际上多个ByteBuf复合成逻辑上的一个ByteBuf
public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements Iterable < ByteBuf > {
private Component[] components; // resized when needed
private static final class Component {
final ByteBuf srcBuf; // the originally added buffer
final ByteBuf buf; // srcBuf unwrapped zero or more times
//这里:构造函数
Component(ByteBuf srcBuf, int srcOffset, ByteBuf buf, int bufOffset, int offset, int len, ByteBuf slice) {
//....
}
}
}
关键信息如上
Component[],每一个Component又包含了ByteBufoffset很明显就是访问对应ByteBuf时候的偏移量了Netty在零拷贝上的原创应该仅仅就是CompositeByteBuf了,其他2处顶多是包装了下NIO的特性。
但是你不能怀疑Netty仍然是java用的最广泛的网络通信框架,绝对值得广大Javaer学习