3.5、ByteBuf
3.5.1、创建
//结果:初始容量256,扩容到512
//PooledUnsafeDirectByteBuf(ridx: 0, widx: 0, cap: 256)
//PooledUnsafeDirectByteBuf(ridx: 0, widx: 300, cap: 512)
public class TestByteBuf {
    public static void main(String[] args) {
        //结果:初始容量256
        //扩容:2倍。。。
        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();//直接内存
        //ByteBuf buf = ByteBufAllocator.DEFAULT.heapBuffer();//堆内存
        //ByteBuf buf = ByteBufAllocator.DEFAULT.directBuffer();//直接内存
        System.out.println(buf.getClass());//查看是否池化
        System.out.println(buf);
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 300; i++) {
            builder.append("a");
        }
        buf.writeBytes(builder.toString().getBytes());
        System.out.println(buf);
    }
}

总结:

  • 直接内存创建和销毁的代价昂贵,但读写性能高(少一次内存复制),适合配合池化功能一起用
  • 直接内存对 GC 压力小,因为这部分内存不受 JVM 垃圾回收的管理,但也要注意及时主动释放
3.5.2、池化

池化的最大意义在于可以重用 ByteBuf,优点有

  • 没有池化,则每次都得创建新的 ByteBuf 实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加 GC 压力
  • 有了池化,则可以重用池中 ByteBuf 实例,并且采用了与 jemalloc 类似的内存分配算法提升分配效率
  • 高并发时,池化功能更节约内存,减少内存溢出的可能

池化,非池化通过idea,vm options来配置

-Dio.netty.allocator.type=unpooled//非池化
-Dio.netty.allocator.type=pooled//池化
3.5.3、组成

image-20221014155705841

废弃部分,可读部分,可写部分,可扩容部分

3.5.4、扩容
  • 如何写入后数据大小未超过 512,则选择下一个 16 的整数倍,例如写入后大小为 12 ,则扩容后 capacity 是 16
  • 如果写入后数据大小超过 512,则选择下一个 2^n,例如写入后大小为 513,则扩容后 capacity 是 210=1024(29=512 已经不够了)
  • 扩容不能超过 max capacity 会报错
3.5.5、读取
buf.readByte();//读一个字节,读过的就废弃了,可读指针移动
//重复度
//1.读之前,做标记
buffer.markReaderIndex();
//2.读取
buffer.readInt();
//3.重复度
buffer.resetReaderIndex();

或者用get通过下标读,不会改变指针

3.5.6、retain & release

由于 Netty 中有堆外内存的 ByteBuf 实现,堆外内存最好是手动来释放,而不是等 GC 垃圾回收。

  • UnpooledHeapByteBuf 使用的是 JVM 内存,只需等 GC 回收内存即可
  • UnpooledDirectByteBuf 使用的就是直接内存了,需要特殊的方法来回收内存
  • PooledByteBuf 和它的子类使用了池化机制,需要更复杂的规则来回收内存

回收内存源码:protected abstract void deallocate()

回收算法:引用计数法,每个方法都实现了 ReferenceCounted 接口

image-20221018104825124

谁来回收呢?一般会想到buf.release。但是,各个handler中间有buf在传递,有想到在tail和head回收,但是有可能buf在传递过程中已经变成了字符串了。

谁是最后使用者,谁负责 release

head和tail释放源码:TailContext类中channelRead()和HeadContext中channel(),释放逻辑:判断如果是Bybuf再release

3.5.7、slice

【零拷贝】的体现之一

小结:

  • duplicate():直接拷贝整个buffer,包括readerIndex、capacity、writerIndex
  • slice():拷贝buffer中已经写入数据的部分
  • copy()方法会进行内存复制工作,效率很低。

对ByteBuf切片,切片后的ByteBuf并没有发生内存复制,而是和切片前ByteBuf共用内存,切片后的ByteBuf有自己独立的read,write指针

slice();//拷贝buffer中已经写入数据的部分
slice(index, length);//从index位置切片,切取length长度

slice后,新的buf和老buf共用内存,新的变了老的也变了。解决:

新buf.retain();引用计数+1 老buf.release();引用计数-1

public class TestSlice {
    public static void main(String[] args) {
        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
        buf.writeBytes("helloworld".getBytes());
        ByteBuf buf1 = buf.slice(0, 5);
        ByteBuf buf2 = buf.slice(5, 5);
        buf1.retain();
        buf.release();
        System.out.println("buf1修改前,buf:" + buf.toString(Charset.defaultCharset()));
        buf1.setByte(0, 'z');
        System.out.println("buf1修改后,buf:" + buf.toString(Charset.defaultCharset()));
        System.out.println("buf1修改后,buf1:" + buf1.toString(Charset.defaultCharset()));
        System.out.println(buf2.toString(Charset.defaultCharset()));
    }
}
3.5.7、duplicate

【零拷贝】的体现之一

直接拷贝整个buffer,包括readerIndex、capacity、writerIndex

3.5.8、copy

会将底层内存数据进行深拷贝,因此无论读写,都与原始 ByteBuf 无关

3.5.9、CompositeByteBuf

【零拷贝】的体现之一

多个 ByteBuf 合并为一个逻辑上的 ByteBuf,避免拷贝

3.5.10、Unpooled

Unpooled 是一个工具类,非池化的 ByteBuf 创建、组合、复制等操作

ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer(5);
buf1.writeBytes(new byte[]{1, 2, 3, 4, 5});
ByteBuf buf2 = ByteBufAllocator.DEFAULT.buffer(5);
buf2.writeBytes(new byte[]{6, 7, 8, 9, 10});

// 当包装 ByteBuf 个数超过一个时, 底层使用了 CompositeByteBuf
ByteBuf buf3 = Unpooled.wrappedBuffer(buf1, buf2);
System.out.println(ByteBufUtil.prettyHexDump(buf3));
3.6.1、ByteBuf 优势
  • 池化 – 可以重用池中 ByteBuf 实例,更节约内存,减少内存溢出的可能
  • 读写指针分离,不需要像 ByteBuffer 一样切换读写模式
  • 可以自动扩容
  • 支持链式调用,使用更流畅
  • 很多地方体现零拷贝,例如 slice、duplicate、CompositeByteBuf

原文地址:http://www.cnblogs.com/jpymll/p/16821450.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性