netty源码之ByteBuf详解

netty源码之ByteBuf详解一 背景简介 ByteBuf 顾名思义 就是字节缓冲区 是 Netty 中非常重要的一个组件 熟悉 jdkNIO 的同学应该知道 ByteBuffer 正是因为 jdk 原生 ByteBuffer 使用比较复杂 某些场景下性能不是太好 netty 开发团队重新设计了 ByteBuf 用以替代原生 ByteBuffer 二 ByteBuf 和 ByteBuffer 对比下面用图示来展示 ByteBuf 和 ByteBuffer 工作原理 ByteBufferBy 依靠 flip 来切换模式 在读模式下

一、背景简介

二、ByteBuf和ByteBuffer对比

下面用图示来展示ByteBuf和ByteBuffer工作原理:

①、ByteBuffer

netty源码之ByteBuf详解

 ByteBuffer依靠flip()来切换模式,在读模式下调用flip()切换为写模式,在写模式下limit和capacity相等,position标识当前写的位置。在写模式下调用flip()切换为读模式,在读模式下position回到起始位置开始读,limit回到position位置表示能读到多少数据,capacity不变表示缓存区容量大小。

capacity:在读/写模式下都是固定的,就是缓冲区容量大小。

position:读/写位置指针,表示当前读(写)到什么位置。

②、ByteBuf

ByteBuf主要是通过readerIndex 和 writerIndex两个指针进行数据的读和写,整个ByteBuf被这两个指针最多分成三个部分,分别是可丢弃部分,可读部分和可写部分

刚初始化的时候,整个缓冲区还没有数据,读写指针都指向0,所有的内容都是可写部分,此时还没有可读部分和可丢弃部分,如下:

netty源码之ByteBuf详解

 当写完N个字节数据后,读指针仍然是0,因为还没有开始进行读事件,写指针向后移动了N个字节的位置,如下:

netty源码之ByteBuf详解

 当开始读数据并且读取M个字节数据之后(M

netty源码之ByteBuf详解

 当可丢弃部分数据被清空之后,readerindex重新回到起始位置,writerindex的位置为writerindex的值减去之前的readerindex,也就是M,相关图示如下:

netty源码之ByteBuf详解

 调用clear之后,writerindex和readerinde全部复位为0。它不会清除缓冲区内容(例如,用填充0),而只是清除两个指针。更改的读写指针的值,每个位置上原本的字节内容并没有发生改变,只是变成了可写状态而已。另请注意,此操作的语义不同于Buffer.clear()。

netty源码之ByteBuf详解

三、源码

明白了ByteBuf工作原理之后,ByteBuf相关的api就很好理解了,在此附上netty官方api文档,以供参阅:

https://netty.io/4.1/api/overview-summary.html。

我们在这里看下netty扩容相关源码逻辑。

public abstract ByteBuf writeBytes(byte[] src);

进入到其抽象子类AbstractByteBuf中。

 @Override public ByteBuf writeBytes(byte[] src) { writeBytes(src, 0, src.length); return this; } @Override public ByteBuf writeBytes(byte[] src, int srcIndex, int length) { ensureAccessible(); ensureWritable(length); setBytes(writerIndex, src, srcIndex, length); writerIndex += length; return this; }

首先ensureAccessible进行安全校验,每种尝试访问缓冲区内容的方法都应调用此方法,以检查缓冲区是否已释放。然后ensureWritable判断是否可写,扩容相关逻辑就在这里进行判断,如果缓冲区可写执行setBytes进行数据写入,然后writerindex向后移动length的位置,最后将ByteBuf对象进行返回。我们重点看ensureWritable。

 @Override public ByteBuf ensureWritable(int minWritableBytes) { if (minWritableBytes < 0) { throw new IllegalArgumentException(String.format( "minWritableBytes: %d (expected: >= 0)", minWritableBytes)); } ensureWritable0(minWritableBytes); return this; }

直接进入ensureWritable0(minWritableBytes)方法中,此时minWritableBytes就是我们计划需要申请的内存大小空间。

 private void ensureWritable0(int minWritableBytes) { // 安全检查,保证写入之前是可访问的 //ensureAccessible(); ​ // 可写,不必扩容 if (minWritableBytes <= writableBytes()) { return; } ​ //下标越界 if (minWritableBytes > maxCapacity - writerIndex) { throw new IndexOutOfBoundsException(String.format( "writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s", writerIndex, minWritableBytes, maxCapacity, this)); } ​ //达到临界条件,开始执行扩容逻辑 // 计算新的容量,实际上为当前容量扩容至2的幂次方大小(具体是多少需要进行后续判断和计算) int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity); // 扩容后的容量 capacity(newCapacity); }

可以看到真正开辟内存空间新容量逻辑处理的是 alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity)执行的,进入到方法里面。

来到其实现类AbstractByteBufAllocator的calculateNewCapacity方法。

 @Override public int calculateNewCapacity(int minNewCapacity, int maxCapacity) { if (minNewCapacity < 0) { throw new IllegalArgumentException("minNewCapacity: " + minNewCapacity + " (expectd: 0+)"); } if (minNewCapacity > maxCapacity) { throw new IllegalArgumentException(String.format( "minNewCapacity: %d (expected: not greater than maxCapacity(%d)", minNewCapacity, maxCapacity)); } // 扩容的阈值,4兆字节大小 final int threshold =  * 4; if (minNewCapacity == threshold) { return threshold; } //如果计划一共需要的内存容量大小大于阈值,则需要和最大容量j进行比较 if (minNewCapacity > threshold) { int newCapacity = minNewCapacity / threshold * threshold; if (newCapacity + threshold > maxCapacity) { newCapacity = maxCapacity; } else { newCapacity += threshold; } return newCapacity; } ​ //如果计划一共需要的内存容量大小小于阈值,则以64为基数进行倍增 int newCapacity = 64; while (newCapacity < minNewCapacity) { newCapacity <<= 1; } return Math.min(newCapacity, maxCapacity); }

minNewCapacity是我们计划一共需要的内存容量大小,maxCapacity是最大缓冲区容量大小。首先判断minNewCapacity 是否小于零或者minNewCapacity 是否大于maxCapacity,满足任一都抛出异常信息,然后判断我们计划一共需要的内存容量大小minNewCapacity 是否等于了阈值4M:

①、如果等于了阈值,新容量大小就是阈值4M。

②、如果计划一共需要的内存容量大小大于阈值,则maxCapacity和minNewCapacity 相对于阈值的整数倍再加上一个阈值进行大小判断,如果大于maxCapacity,则新容量最大就是maxCapacity,返回maxCapacity,如果小于maxCapacity,则相当于按照阈值的2倍进行扩容。

③、如果计划一共需要的内存容量大小小于阈值,则以64为基数只要小于我们计划需要的内存容量大小,就2倍扩容,最后选取循环后的扩容值和最大值两个值其中的较小者。

4、ByteBuf主要的继承关系

netty源码之ByteBuf详解

从内存分配的角度看,ByteBuf可以分为两类

(1)堆内存(HeapByteBuf)字节缓冲区:特点是内存的分配和回收速度快,可以被JVM自动收回;缺点就是如果进行Socket的I/O读写,需要额外做一次内存复制,将堆内存对应的缓冲区复制到内核Chanenel中,性能会有一定程度的下降。

(2)直接内存(DirectByteBuf) 字节缓冲区:非堆内存,它在堆外进行内存分配,相比于堆内存,它的分配和回收速度会慢一些,但是将它写入或者从Socket Channel中读取时,由于少了一次内存复制,速度比堆内存快。

正式因为各有利弊,所以Netty提供了多种ByteBuf供开发者使用,经验表明,ByteBuf的最佳实践是在I/O通信线程的读写缓冲区使用DirectByteBuf,后端业务消息的编解码模块使用HeapByteBuf,这样组合可以达到性能最优。

从内存回收角度看,ByteBuf也可以分为两类:基于对象池的ByteBuf和普通ByteBuf。两者的主要区别就是基于对象池的ByteBuf可以重用ByteBuf对象,它自己维护了一个内存池,可以循环利用创建的ByteBuf,提升内存的使用效率,降低由于高负载导致的频繁GC。测试表名使用内存池后的Netty在高负载、大并发的冲击下内存和GC更加平稳。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/229210.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午5:13
下一篇 2026年3月16日 下午5:13


相关推荐

  • 【详细】国内网站备案流程与步骤

    【详细】国内网站备案流程与步骤完成备案需要多久时间 国内网站备案流程与步骤大讲解

    2026年3月20日
    2
  • Radius认证协议(一)

    Radius认证协议(一)简介为大量用户管理分散的串行线和调制解调器池需要重要的管理支持 由于调制解调器池定义为与外部世界的链接 因此需要特别关注其安全 授权和计费 这可以通过管理单个的用户数据库来实现 该数据库可以进行认证 验证用户名和密码 以及向用户提供的服务类型的配置信息 例如 SLIP PPP telnet rlogin Radius 主要特征如下 C S 模型 网络接入服务器 NAS 是 Radius 的客户端 负责将用户信息传递给指定的 RADIUS 服务器 然后对返回的响应进行相应的操作

    2026年3月18日
    2
  • java向上取整和向下取整,万字长文!

    java向上取整和向下取整,万字长文!一面:70分钟突击电话面试正思考着项目功能模块,阿里面试官打来了电话,开始了阿里一面。阿里面试官自我介绍,介绍了5分钟左右,部门的情况,主要的业务提问开始会哪些操作系统Linux会一点说一下操作指令,怎么看cpu,看进程,看端口操作系统进程间通信追问了一个信号相关的问题,我不知道了。io多路复用,说一说面向切面编程,说一说那些场景说说面向切面编程给一个场景,有很多方法,找出耗时长的方法spring的@autowired的作用mybatis和hibernate的区别C,C

    2022年6月21日
    30
  • 消息队列

    消息队列

    2021年3月12日
    163
  • java jshell_JDK11 | 第二篇 : JShell 工具

    java jshell_JDK11 | 第二篇 : JShell 工具一 简介 JavaShell 工具是 JDK1 9 出现的工具 JavaShell 工具 JShell 是一个用于学习 Java 编程语言和 Java 代码原型的交互式工具 JShell 是一个 Read Evaluate Print 循环 REPL 它在输入时评估声明 语句和表达式 并立即显示结果 该工具从命令行运行 二 为什么要使用 JShell 使用 JShell 您可以一次输入一个程序元素 立即查看结果 并

    2026年3月17日
    1
  • Error: org.apache.axis2.AxisFault at org.apache.axis2.AxisFault.makeFault(AxisFault.java:430) at 的原因

    Error: org.apache.axis2.AxisFault at org.apache.axis2.AxisFault.makeFault(AxisFault.java:430) at 的原因Error:org.apache.axis2.AxisFaultatorg.apache.axis2.AxisFault.makeFault(AxisFault.java:430)atorg.apache.axis2.description.AxisService.createService(AxisService.java:2504)atorg.apache.axis2.des

    2025年9月5日
    6

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号