Netty的DirectByteBuf的零复制,仅仅是减少了Linux进程内部的内存复制,还没有上升到内核的维度。
Netty的DirectByteBuf的零复制,仅仅是减少了JVM爱冲区到直接的内存复制
只有第五条属于操作系统才层面的零复制
Netty的零拷贝主要体现在以下五个方面:
- Netty提供CompositeByteBuf类,可以将多个ByteBuf合并为个逻辑 上的ByteBuf,避免了各个ByteBuf之间的拷贝。
- 通过wrap操作,我们可以将byte数组、 ByteBuf、 ByteBuffer等包装成个Netty ByteBuf对象,进而避免贝操作。
- ByteBuf支持slice操作,可以将ByteBu分解为多个共享同个存储区域的ByteBuf,避免内存的拷贝。
- Netty使用直接内存,消息在发送过程中少了一次缓冲区的内存拷贝。
- Netty的文件传输调用FileRegion包装的tranaferTo方法,可以直接将文件缓冲区的数据发送到目标Channel,避免通过循环write方式导致的内存拷贝问题。
1、CompositeByteBuf
CompositeByteBuf可以把需要合并的多个ByteBuf组合起来,对外提供统一的readIndex和writerIndex。
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.CompositeByteBuf;
import io.netty.util.internal.StringUtil;
/**
* @Description:
* @Author: zhangjx
* @Date: 2021-06-13
**/
public class TestCompositeByteBuf {
public static void main(String[] args) {
ByteBuf buffer1 = ByteBufAllocator.DEFAULT.buffer();
buffer1.writeBytes(new byte[]{'a','b','c'});
ByteBuf buffer2 = ByteBufAllocator.DEFAULT.buffer();
buffer2.writeBytes(new byte[]{'d','e','f'});
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
//会发生两次数据复制
buffer.writeBytes(buffer1).writeBytes(buffer2);
log(buffer);
//需要注意引用计数问题
CompositeByteBuf compositeByteBuf = ByteBufAllocator.DEFAULT.compositeBuffer();
buffer1.retain();
buffer2.retain();
//默认不会调整读写指针位置
compositeByteBuf.addComponents(buffer1,buffer2);
//会调整读写指针位置
compositeByteBuf.addComponents(true,buffer1,buffer2);
log(compositeByteBuf);
buffer1.release();
buffer2.release();
}
private static void log(ByteBuf buffer) {
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
.append(StringUtil.NEWLINE);
ByteBufUtil.appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
}
}
2、Unpooled
Unpooled是一个工具类,提供了非池化的Bytebuf创建、组合、复制等操作。其中提供了一系列的wrap包装方法,可以方便快速的包装出CompositeByteBuf实例或者ByteBuf实例,而不用进行内存拷贝。
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.util.internal.StringUtil;
/**
* @Description:
* @Author: zhangjx
* @Date: 2021-06-13
**/
public class TestUnpooled {
public static void main(String[] args) {
ByteBuf buffer1 = ByteBufAllocator.DEFAULT.buffer();
buffer1.writeBytes(new byte[]{'a','b','c'});
ByteBuf buffer2 = ByteBufAllocator.DEFAULT.buffer();
buffer2.writeBytes(new byte[]{'d','e','f'});
ByteBuf byteBuf = Unpooled.wrappedBuffer(buffer1, buffer2);
log(byteBuf);
}
private static void log(ByteBuf buffer) {
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
.append(StringUtil.NEWLINE);
ByteBufUtil.appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
}
}
3、ByteBuf浅层复制
浅层复制可以很大程度避免内存复制。
浅层复制分两种:
- 切片(slice)浅层复制
- 整体(duplicate)浅层复制
3.1、切片浅层复制
ByteBuf的slice方法可以获取一个ByteBuf的切片,一个ByteBuf可以多次切片浅层复制,多次切片后的ByteBuf对象可以共享一个内存区域
切片方法:
public ByteBuf slice()
返回ByteBuf实例中可读部分的切片public ByteBuf slice(int var1, int var2)
通过设置不同起始位置和长度获取ByteBuf不同区域的切片
切片特点:
- 切片不可以写入,因为读写指针相同
- 切片不会复制源ByteBuf的底层数据,底层数组和源ByteBuf的底层数组是同一个
- 切片不会改变源ByteBuf的引用计数,调用浅层复制实例时,手动调用retain()方法增加引用计数
package com.jx.netty.netty.bytebuf;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
import static io.netty.util.internal.StringUtil.NEWLINE;
/**
* @Description:
* @Author: zhangjx
* @Date: 2021-06-09
**/
public class TestSlice {
public static void main(String[] args) {
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(12);
buffer.writeBytes(new byte[]{'a','b','c','d','e','f','g','h','i','j'});
log(buffer);
//slice()返回了ByteBuf可读部分切片
System.out.println("=============================");
ByteBuf slice = buffer.slice();
log(slice);
System.out.println("=============================");
ByteBuf buf1 = buffer.slice(0, 5);
buf1.retain();
ByteBuf buf2 = buffer.slice(5, 5);
//手动增加引用计数
buf2.retain();
log(buf1);
log(buf2);
System.out.println("=============================");
buffer.setByte(0,'x');
buffer.setByte(5,'x');
log(buf1);
log(buf2);
System.out.println("=============================");
buffer.release();
log(buf1);
log(buf2);
buf1.release();
buf2.release();
}
private static void log(ByteBuf buffer) {
int length = buffer.readableBytes();
int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
StringBuilder buf = new StringBuilder(rows * 80 * 2)
.append("read index:").append(buffer.readerIndex())
.append(" write index:").append(buffer.writerIndex())
.append(" capacity:").append(buffer.capacity())
.append(NEWLINE);
appendPrettyHexDump(buf, buffer);
System.out.println(buf.toString());
}
}
3.2、整体浅层复制
duplicate()返回的是整个对象的浅层复制:
- duplicate()返回实例读写指针,最大容量和源ByteBuf相同
- duplicate()不会改变源ByteBuf引用计数
- duplicate()不会复制源ByteBuf的底层数据
public static void main(String[] args) {
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(12);
buffer.writeBytes(new byte[]{'a','b','c','d','e','f','g','h','i','j'});
log(buffer);
//整体浅层复制
ByteBuf duplicate = buffer.duplicate();
log(duplicate);
}