Netty中的ByteBuf使用介绍

ByteBuf有三类:

  • 堆缓存区:JVM堆内存分配
  • 直接缓冲区:有计算机内存分配,JVM只是保留分配内存的地址信息,相对于堆内存方式较为昂贵;
  • 复合缓冲区:复合缓冲区CompositeByteBuf,它为多个ByteBuf 提供一个聚合视图。比如HTTP 协议,分为消息头和消息体,这两部分可能由应用程序的不同模块产生,各有各的 ByteBuf,将会在消息被发送的时候组装为一个ByteBuf,此时可以将这两个ByteBuf聚 合为一个CompositeByteBuf,然后使用统一和通用的ByteBuf API来操作;

ByteBufAllocator

当在需要ByteBuf时,用这个类进行获取,它提供了3中类型的ByteBuf获取。

    // 返回一个基于堆或直接内存的ByteBuf
ByteBuf buffer();
    ByteBuf buffer(int initialCapacity);
    ByteBuf buffer(int initialCapacity, int maxCapacity);
// 返回一个适用于IO操作的ByteBuf
    ByteBuf ioBuffer();
    ByteBuf ioBuffer(int initialCapacity);
    ByteBuf ioBuffer(int initialCapacity, int maxCapacity);
// 返回一个基于堆内存的ByteBuf
    ByteBuf heapBuffer();
    ByteBuf heapBuffer(int initialCapacity);
    ByteBuf heapBuffer(int initialCapacity, int maxCapacity);
// 返回一个基于直接内存的ByteBuf
    ByteBuf directBuffer();
    ByteBuf directBuffer(int initialCapacity);
    ByteBuf directBuffer(int initialCapacity, int maxCapacity);
// 返回一个包含指定数量的ByteBuf的复合ByteBuf
    CompositeByteBuf compositeBuffer();
    CompositeByteBuf compositeBuffer(int maxNumComponents);
// 返回一个包含指定数量的堆内存ByteBuf的负荷ByteBuf
    CompositeByteBuf compositeHeapBuffer();
    CompositeByteBuf compositeHeapBuffer(int maxNumComponents);
// 返回一个包含指定数量的直接内存ByteBuf的负荷ByteBuf
    CompositeByteBuf compositeDirectBuffer();
    CompositeByteBuf compositeDirectBuffer(int maxNumComponents);
// 判断是否池化的直接内存对象
    boolean isDirectBufferPooled();
// 根据最小和最大容量计算出一个新的容量
    int calculateNewCapacity(int minNewCapacity, int maxCapacity);

netty中使用方式例如下面再入站里的handler调用:

    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
        System.out.println("客户端收到:" + byteBuf.toString(CharsetUtil.UTF_8));
        ByteBuf bb = channelHandlerContext.alloc().heapBuffer();
        ByteBuf db = channelHandlerContext.alloc().directBuffer();
        channelHandlerContext.channel();
    }

它由上下文对象ChannelHandlerContext调用alloc()方法获取ByteBufAllocator

API

我们先看下下面这几个API,需要熟悉理解的:

// 返回一个ByteBufAllocator,创建ByteBuf使用
public abstract ByteBufAllocator alloc();
// 返回可以被读取的字节的开始索引
 public abstract int readerIndex();
public abstract ByteBuf readerIndex(int readerIndex);
// 返回可被写入字节的开始索引
  public abstract int writerIndex();
   public abstract ByteBuf writerIndex(int writerIndex);
// 可被读取的字节数
    public abstract int readableBytes();
// 可被写入的字节数
    public abstract int writableBytes();
// 是否可读
    public abstract boolean isReadable();
// 是否可读,参数是是否可读入指定字节数
    public abstract boolean isReadable(int size);
// 是否可写
    public abstract boolean isWritable();
// 是否可写,参数是是否可读入指定字节数
    public abstract boolean isWritable(int size);
// 清空数据
    public abstract ByteBuf clear();
// 标记当前的可被读取的开始索引
 public abstract ByteBuf markReaderIndex();
// 重置可被读取的索引,就是重置为标记的索引,或是0
    public abstract ByteBuf resetReaderIndex();
// 标记可被写入的开始索引
    public abstract ByteBuf markWriterIndex();
// 重置可被写入的索引,就是重置为标记的索引,或是0
    public abstract ByteBuf resetWriterIndex();
// 丢弃读取过的字节(0到readerIndex的部分)
    public abstract ByteBuf discardReadBytes();

虽然上面注释有写过,但还是再提醒一遍;

readerIndex表示可以被读取数据的开始索引,或者说已经读取了readerIndex个字节;
writerIndex表示可以被写入数据的开始索引,或者说已经写入了writerIndex个字节;

discardReadBytes丢弃的是读取过的字节数据,同时writerIndex会相应减少对应的字节长度;

看几个例子,再次加深记忆:

 ByteBuf byteBuf = new PooledByteBufAllocator().buffer();

        System.out.println("--------------测试get/set 与 read/write方法的区别");
        byteBuf.setBytes(0, "qwer".getBytes());
        System.out.println("数据:" + byteBuf.toString(CharsetUtil.UTF_8));

        System.out.println("set 之后 readIndex:" + byteBuf.readerIndex());
        System.out.println("set 之后 wirteIndex:" + byteBuf.writerIndex());

        System.out.println("get 之后 readIndex:" + byteBuf.readerIndex());
        System.out.println("get 之后 wirteIndex:" + byteBuf.writerIndex());

        // 没有数据被写进去
        System.out.println(byteBuf.toString(CharsetUtil.UTF_8));
        // 写入12个字节数据,writerIndex=12
        byteBuf.writeBytes("天气不错".getBytes(CharsetUtil.UTF_8));
        System.out.println("数据:" + byteBuf.toString(CharsetUtil.UTF_8));
        // 没有读取,readerIndex=0
        System.out.println("write 之后 readIndex:" + byteBuf.readerIndex());
        System.out.println("write 之后 wirteIndex:" + byteBuf.writerIndex());
        // get方式获取字节,readerIndex不会移动
        byteBuf.getByte(3);
        System.out.println("get 之后 readIndex:" + byteBuf.readerIndex());
        System.out.println("get 之后 wirteIndex:" + byteBuf.writerIndex());
        // read方式读取,readerIndex=3,没有涉及写入,writerIndex不变
        byteBuf.readBytes(3);
        System.out.println("read 之后 readIndex:" + byteBuf.readerIndex());
        System.out.println("read 之后 wirteIndex:" + byteBuf.writerIndex());
        // 因为读取了3个字节(一个汉字),可被读取的数据从第二个汉字开始
        System.out.println("数据:" + byteBuf.toString(CharsetUtil.UTF_8));
        // 容量256
        System.out.println("容量:" + byteBuf.capacity());
        // 将数据的第6个索引开始替换为指定的字节数据,注意,这个长度要在指定索引和writerIndex差值内,不然会报异常(因为没有数据可以被操作)
        byteBuf.setBytes(6, "123".getBytes());
        System.out.println("setBytes 之后:" + byteBuf.toString(CharsetUtil.UTF_8));

        System.out.println("-------------测试byteBuf其他的一些方法");
        System.out.println("readableBytes 可被读取的字节数:" + byteBuf.readableBytes());
        System.out.println("writableBytes 可被写入的字节数:" + byteBuf.writableBytes());
        System.out.println("isReadable 是否可读:" + byteBuf.isReadable());
        System.out.println("isWritable 是否可写:" + byteBuf.isWritable());

        System.out.println("-----------测试标记与重置");
        // 重置也就是readerIndex=writerIndex=0
        byteBuf.resetReaderIndex();
        byteBuf.resetWriterIndex();

        System.out.println("reset 之后 readIndex:" + byteBuf.readerIndex());
        System.out.println("reset 之后 wirteIndex:" + byteBuf.writerIndex());
        // 重新写入数据,测试后面的方法
        byteBuf.writeBytes("天气真好".getBytes(CharsetUtil.UTF_8));
        // 再次读取3个字节
        byteBuf.readBytes(3);
        // 标记当前的readerIndex
        byteBuf.markReaderIndex();
        // 标记当前的writerIndex
        byteBuf.markWriterIndex();
        // 重置,只会重置为上一次mark的索引
        byteBuf.resetReaderIndex();
        byteBuf.resetWriterIndex();

        System.out.println("mark-reset 之后 readIndex:" + byteBuf.readerIndex());
        System.out.println("mark-reset 之后 wirteIndex:" + byteBuf.writerIndex());

        System.out.println("-------------测试丢弃");
        // 丢弃数据,释放内存,原来是写入了12个字节,writerIndex=12,执行丢弃,会把已经读取的丢弃(3个字节)
        // 所以,执行后的writerIndex=9,readerIndex=0
        byteBuf.discardReadBytes();

        System.out.println("容量:" + byteBuf.capacity());

        System.out.println("丢弃 之后 readIndex:" + byteBuf.readerIndex());
        System.out.println("丢弃 之后 wirteIndex:" + byteBuf.writerIndex());

结果如下:

image-20240528001333652

对于上面的操作,可以看下面这个图解:

image-20240528002756774

资源的释放

资源释放针对的主要是ByteBuf这个对象;

为什么说要释放ByteBuf这个对象,这个对象不是在方法中被创建的吗,方法结束后不就会被JVM回收吗?

如果说ByteBuf是一般对象的话,这个说法是对的,可是,这个对象ByteBufnetty实现的,并且实现于ReferenceCounted,而这个接口是用于引用计数管理对象生命周期的,需要我们手动进行计数管理;

我们看下这个接口提供的方法,对这个管理便会更加清晰:

public interface ReferenceCounted {
    /**
     * 返回对象的引用计数; 如果计数=0,表示对象不被引用可以被安全回收
     */
    int refCnt();

    /**
     * 引用计数+1
     */
    ReferenceCounted retain();

    /**
     * 引用计数+increment(增加指定的计数)
     */
    ReferenceCounted retain(int increment);

    /**
     * 记录当前的访问位置;
     * 如果发生内存泄漏,返由 ResourceLeakDetector(资源泄漏探测器)返回这些信息
     */
    ReferenceCounted touch();

    /**
     * 记录当前的访问位置,以及额外的信息
     */
    ReferenceCounted touch(Object hint);

    /**
     * 引用次数-1;释放当前资源
     */
    boolean release();

    /**
     * 引用次数-decrement(减少指定计数)
     */
    boolean release(int decrement);
}

那为什么netty要实现这么一个需要手动释放的对象?

主要几点:

  • 优化内存管理:ByteBuf支持池化(Pooled),可以重用之前分配,但已回收的内存块,减少内存分配和垃圾回收的开销;非池化(Unpooled)每次使用时都要创建对象实例,分配内存,相对于池化对象,它过于频繁的分配内存和释放操作;
  • 引用计数机制/性能提升:更精准的控制对象的生命周期,在JVM中,利用各种算法,如标记清除、标记整理、复制等算法决定哪些对象可以被回收,并且在某些场景下,如一个方法中的创建并且被使用的变量,需要在变量离开作用域或方法执行完,也或是被明确复制为null时,才能被判定为无引用,而ByteBuf可以决定什么时候不被引用,做到在需要时及时回收,提高系统整体性能和响应能力;
  • 诊断内存泄漏:netty提供了ResourceLeakDetector类来跟踪ByteBuf的分配,在检测到内存泄漏时打印相关日志信息;

有人会问:netty这个框架不就是为了方便于开发,对socket进行封装,对业务流程步骤进行抽象,它就不能做到自动释放?

哎,netty确实对ByteBuf做了自动释放,只是ByteBufhandler之间流转时,这个经过业务处理,可能已经不是原来的ByteBuf,这个过程中可能创建了新的ByteBuf,而旧的ByteBuf就需要我们手动释放;

piple中有一个handler链,我们可以自由添加handler,但是头尾handler都是默认添加的,我们来看下面代码:

image-20240526210421409

这部分是piple实例化时执行的,它默认会添加TailContextHeadContext两个handler,尾部的handler就负责释放ByteBuf对象,也就是在这个handler链中,除了我们自己添加的handler,还有两个handler分别在头部和尾部,而尾部的handler其中一个功能就是释放handler链中传递的ByteBuf对象。

位置:io.netty.channel.DefaultChannelPipeline.TailContext#channelRead

image-20240526212947226

image-20240526213015187

可以看到ReferenceCountUtil.release(msg);的,这里就是释放对象的地方;

ReferenceCountUtil这个是netty自己封装的用于处理实现了引用计数接口对象的工具类。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/688447.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

6月26~28日,2024北京国际消防展即将开幕!

随着社会的快速发展,消防安全日益受到广大民众的高度关注。为了进一步推动消防科技的创新与发展,提升全民消防安全意识,2024年北京消防展将于6月26日在北京国家会议中心盛大开展。目前:观众预登记已全面启动,广大市民和业界人士可…

Skins

本主题解释如何将DevExpress主题/皮肤应用到应用程序中,如何允许用户在运行时在主题之间切换,如何自定义现有皮肤或创建自己的皮肤,等等。 WinForms订阅包括许多基本控件:按钮、复选框、表单、消息框、对话框、对话框等。 我们实现…

python面向过程与初始面向对象编程

让我们穿越到《龙珠》世界,一起揭开 面向对象编程 的神秘面纱吧。 面向过程编程与面向对象编程 天下第一武道会 选手登记 第 22 届天下第一武道会即将召开,各路武术高手齐聚一堂,其中最受瞩目的,当属卡卡罗特(孙悟…

Docker高级篇之Docker搭建mysql主从复制架构

文章目录 1. 安装mysql主从复制2. 主从复制测试 1. 安装mysql主从复制 首先创建主节点 docker run -d -p 3308:3306 \ --privilegedtrue \ -v /Users/jackchai/Desktop/lottory_docker/learndocker/mymysql/master/log:/var/log/mysql \ -v /Users/jackchai/Desktop/lottory_…

dots_image 增强图像中的圆点特征

dots_image 增强图像中的圆点特征 1. dot_image 有什么用途?2. 点状字符的特征增强3. Halcon代码 1. dot_image 有什么用途? Enhance circular dots in an image. 这个算子可以增强图像中的圆点特征,例如下面的例子。 2. 点状字符的特征增强…

【数据结构与算法 | 二叉树篇】力扣101, 104, 111,LCR144

1. 力扣101 : 对称二叉树 (1). 题 给你一个二叉树的根节点 root , 检查它是否轴对称。 示例 1: 输入:root [1,2,2,3,4,4,3] 输出:true示例 2: 输入:root [1,2,2,null,3,null,3] 输出:false…

知识图谱的应用---智慧政务

文章目录 智慧政务典型应用 智慧政务 智慧政务即通过“互联网政务服务”构建智慧型政府,利用云计算、移动物联网、人工智能、数据挖掘、知识管理等技术,提高政府在办公、监管、服务、决策中的智能水平,形成高效、敏捷、公开、便民的新型政府&…

微前端之旅:探索Qiankun的实践经验

theme: devui-blue 什么是微前端? 微前端是一种前端架构方法,它借鉴了微服务的架构理念,将一个庞大的前端应用拆分为多个独立灵活的小型应用,每个应用都可以独立开发、独立运行、独立部署,再将这些小型应用联合为一个完…

3D打印随形水路:模具水路的革命性技术

在快速发展的模具制造行业中,3D打印技术以其独特的优势正在引领一场技术革命。其中,3D打印随形水路技术,凭借其灵活性和定制化设计的能力,为模具带来了前所未有的变革。 模具3D打印随形水路技术,是一种利用3D打印技术制…

环 境 变 量

如果希望某一个文件在 CMD 窗口的任意路径下都可以打开,则需要将该文件的路径存放在环境变量中。 在 CMD 中运行该文件时,优先查看当前路径下的文件,如果没有找到,则进入环境变量中记录的路径下寻找该文件,如果能找到…

阿里通义千问,彻底爆了!(本地部署+实测)

点击“终码一生”,关注,置顶公众号 每日技术干货,第一时间送达! 问大家一个问题:你是否想过在自己的电脑上部署一套大模型?并用自己的知识库训练他? 阿里通义千问今天发布了最新的开源大模型系…

灵动岛动效:打造沉浸式用户体验

灵动岛是专属于 iPhone 14 Pro 系列交互UI,通过通知消息的展示和状态的查看与硬件相结合,让 iPhone 14 Pro 系列的前置摄像头和传感器的“感叹号”,发生不同形状的变化。这样做的好处是让虚拟软件和硬件的交互变得更为流畅,以便让…

M1Pro 使用跳板机

Mac (M1 Pro) 通过Iterm2 使用跳板机 1、由于堡垒机(跳板机)不能支持mac系统终端工具,只支持xshell等win生态。所以我们需要先安装iterm2 装iterms教程 这里头对rz、sz的配置不详细。我们可以这样配置: where iterm2-send-zmod…

关闭文件及使用with语句

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 1 关闭文件 打开文件后,需要及时关闭,以免对文件造成不必要的破坏。关闭文件可以使用文件对象的close()方法实现。close()方…

网络安全实验BUAA-全套实验报告打包

下面是部分BUAA网络安全实验✅的实验内容 : 认识路由器、交换机。掌握路由器配置的基本指令。掌握正确配置路由器的方法,使网络正常工作。 本博客包括网络安全课程所有的实验报告:内容详细,一次下载打包 实验1-路由器配置实验2-AP…

Linux存储管理

简介 硬件上的存储设备目前有两类,通过磁头读写信息的机械硬盘和用主控芯片将信息写入晶体管的固态硬盘,硬盘调度算法等知识可以通过前面的操作系统设备管理文章学习,本章只介绍Linux中能对存储设备的操作。 为了让操作系统识别和管理物理磁…

SAP ERP系统主要模块简介

SAP系统通过提供一系列高度灵活的模块,满足企业在不同业务领域的需求。这些模块不仅功能齐全且相对独立,但它们之间又能紧密协作,共同构筑一个协同高效的工作环境。 财务会计(FI)模块 它涵盖了总账、应收账款、应付账…

React@16.x(21)渲染流程-更新

目录 1,更新的2种场景2,节点更新3,对比 diff 更新3.1,React 的假设3.1.2,key 2.1,找到了对比的目标2.1.1,节点类型一致1,空节点2,DOM节点3,文本节点4&#xf…

通俗易懂的解释保护性看跌期权和抛补看涨期权!

今天带你了解通俗易懂的解释保护性看跌期权和抛补看涨期权!当涉及期权交易时,保护性看跌期权和抛补看涨期权是两种常见的策略,它们的目的都是为了在特定市场情况下对投资进行保护或增强收益。 保护性看跌期权 保护性看跌期权是一种风险管理策…

第八篇——矢量化:象形文字和拼音文字是如何演化的?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么? 四、总结五、升华 一、背景介绍 通过这篇看似在讲文字的演化过程,实际是在说人生应该如何走&a…