ByteBuf

ByteBuf需要提供JDK ByteBuffer的功能(包含且不限于),主要有以下几类基本功能:

7种Java基础类型、byte[]、ByteBuffer(ByteBuf)的等的读写

缓冲区自身的copy和slice

设置网络字节序

构造缓冲区实例

操作位置指针

扩容原理

首先确认ByteBuf是否已经被释放,如果被释放,则抛出IllegalReferenceCountException异常

判断写入需要的最小空间,如果该空间小于ByteBuf的可写入空间,直接返回,不进行扩容

判断写入需要的最小空间,如果该空间大于ByteBuf的(最大容量-当前的写索引),不进行扩容,抛出IndexOutOfBoundsException异常

计算新容量,动态扩容的规则,当新容量大于4MB时,以4MB的方式递增扩容,在小于4MB时,从64字节开始倍增(Double)扩容

读写索引

Netty提供readIndex和writeIndex用来支持读取和写入操作,两个索引将缓冲区划分为三个区域

0 ~ readIndex:已读区域(可丢弃区域)

readIndex ~ writeIndex:未读取区域

writeIndex ~ capacity:待写入区域

已读区域(Discardable Bytes)

位于已读区域的内容表明该内容已被Netty处理完成,我们可以重用这块缓冲区,尽量减少缓冲区的动态扩容(复制,耗时操作)。

调用discardBytes()方法可以清除已读区域内容,但同时会导致未读区域的左移,也是将未读区域的内容复制到原来的已读区域(耗时),

因此频繁的调用discardBytes也是不可取的,可以根据实际情况进行调用。

Readable Bytes和Writable Bytes

Readable Bytes(可读空间)存储的是未被读取处理的内容,以read或者skip开头的方法都会从readIndex开始读取或者跳过指定的数据,同时readIndex会增加读取或跳过

的字节数长度。如果读取的字节数长度大于实际可读取的字节数,抛出IndexOutOfBoundsException异常。

Writable Bytes(可写入空间)是未被数据填充的缓冲区块,以write开头的操作都会从writeIndex开始向缓冲区写入数据,同时writeIndex会增加写入的数据的字节数长度。

如果写入的字节数大于可写入的字节数,会抛出IndexOutOfBoundsException异常。

Clear

Clear操作并不会清除缓冲区的内容,只是将readIndex和writeIndex还原为初始分配值。

Mark和Reset

markReadIndex

resetReadIndex

markWriteIndex

resetWriteIndex

查找操作

indexOf(int fromIndex, int toIndex, byte value):fromIndex<=toIndex时,从头开始查找首次出现value的位置(查找范围fromIndex ~ toIndex),当fromIndex > toIndex时,倒着查找首次出现value的位置(查找的范围toIndex ~ fromIndex - 1),查不到返回-1

bytesBefore(byte value):从ByteBuf的可读区域中首次定位出现value的位置,没有找到返回-1。该方法不会修改readIndex和writeIndex

bytesBefore(int length, byte value):从ByteBuf的可读区域中定位首次出现value的位置,结束索引是readIndex+length。如果length大于可读字节数,抛出IndexOutOfBoundsException异常

bytesBefore(int index, int length, byte value):从ByteBuf中定位首次出现value的位置,起始索引为index,结束索引为index+length,如果index+length大于当前缓冲区的容量,抛出IndexOutOfBoundsException异常

forEachByte(int index, int length, ByteProcessor processor):从index开始,到index + length结束,与ByteProcessor设置的查找条件进行对比,满足条件,返回位置索引,否则返回-1

forEachByteDesc(ByteProcessor processor):倒序遍历ByteBuf的可读字节数组,与ByteProcessor设置的查找条件进行对比,满足条件,返回位置索引,否则返回-1

forEachByteDesc(int index, int length, ByteProcessor processor):以index + length - 1开始,直到index结束,倒序遍历ByteBuf字节数组,与ByteProcessor设置的查找条件进行对比,满足条件,返回位置索引,否则返回-1

Netty提供了大量的默认的ByteProcessor,来对常用的查找自己进行查找,具体可见ByteProcessor接口。

Derived buffers(派生缓冲区)

duplicate():返回当前ByteBuf的复制对象,复制后返回的ByteBuf与操作的ByteBuf共享缓冲区内容,但是维护自己独立的读写索引。当修改复制后的ByteBuf内容后,原ByteBuf的内容也随之改变,因为双方持有的是同一个内容的指针引用。

copy():复制一个新的ByteBuf对象,内容和索引都与原ByteBuf独立,复制操作本身并不修改原ByteBuf的读写索引

copy(int index, int length):复制一个新的ByteBuf对象,复制开始的索引为index,复制的长度为length

slice():返回与当前ByteBuf的可读子缓冲区,范围是readIndex ~ writeIndex,返回后的ByteBuf与原ByteBuf内容共享,读写索引独立维护,maxCapacity是当前ByteBuf的可读字节数(换句话说就是这个新返回的缓冲区不能再进行写入)

slice(int index, int length):返回index开始,length长度的当前ByteBuf的子缓冲区,返回后的ByteBuf与原ByteBuf内容共享,读写索引独立维护,maxCapacity是length(换句话说就是这个新返回的缓冲区不能再进行写入)

转换成标准的ByteBuffer

ByteBuffer nioBuffer():将当前ByteBuf可读的缓冲区转换成ByteBuffer,两者共享同一个缓冲区内容引用,对ByteBuffer的读写操作并不会修改原ByteBuf的读写索引。返回后的ByteBuffer无法感知ByteBuf的动态扩展。

ByteBuffer nioBuffer(int index, int length):从ByteBuf的index位置开始长度为length的缓冲区转换成ByteBuffer,两者共享同一个缓冲区内容引用,对ByteBuffer的读写操作并不会修改原ByteBuf的读写索引。返回后的ByteBuffer无法感知ByteBuf的动态扩展。

随机读写

主要通过set和get开头的方法,这两个方法可以指定索引位置。

ByteBuf源码

从内存分配的角度来看,ByteBuf主要分为以下两类:

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

直接内存(DirectByteBuf)字节缓冲区:堆外内存直接分配,相比于堆内存,分配和回收速度比较慢,但是在Socket Channel中进行读写比较快(少一次内存复制)

ByteBuf的最佳时间是在I/O通信线程的读写缓冲区使用DirectByteBuf,后端业务消息的编解码模块使用HeapByteBuf。

从内存回收的角度进行分类:

基于对象池的ByteBuf:自己维护了一个内存池,可以重复利用ByteBuf对象,提升内存使用率,降低GC频率

普通的ByteBuf

AbstractByteBuf

AbstractByteBuf继承ByteBuf,ByteBuf中的一些公共属性和方法会在AbstractByteBuf中实现。

主要变量

ResourceLeakDetector leakDetector对象:被定义为static,所有的ByteBuf实例共享一个ResourceLeakDetector leakDetector对象。ResourceLeakDetector主要用来检测对象是否泄漏。

索引设置:读写索引、重置读写索引、最大容量

读操作

读操作的公共功能由父类实现,差异化由具体的子类实现。

选取readBytes(byte[] dst, int dstIndex, int length)分析:

首先对缓冲区可读空间进行校验:如果读取的长度(length) < 0,会抛出IllegalArgumentException异常;如果可读的字节数小于需要读取的长度(length),会抛出IndexOutOfBoundsException异常

校验通过之后,调用getBytes方法从当前的读索引开始进行读取(这一块就需要由真正的子类来各自实现),复制length个字节到目标byte数组,数组开始的位置是dstIndex

读取成功后,对读索引进行递增,增加的长度为length

写操作

写操作的公共功能由父类实现,差异化由具体的子类实现。

选取writeBytes(byte[] src, int srcIndex, int length)分析:

首先对缓冲区的可写空间进行校验:如果要写入的长度(length) < 0,会抛出IllegalArgumentException异常;如果要写入的长度小于缓冲区可写入的字节数,表明可写;如果要写入的长度 > 最大容量 - writeIndex,会抛出IndexOutOfBoundsException;否则进行扩容操作(扩容操作的原理前面已经讲过)。

操作索引

与索引相关的操作主要涉及设置读写索引、mark、和reset等。

选取readerIndex(int readerIndex)进行分析:

首先对索引合法性进行判断:如果readerIndex小于0或者readerIndex > writeIndex,则抛出IndexOutOfBoundsException异常

校验通过之后,将读索引设置为readerIndex

重用缓冲区

选取discardReadBytes()进行分析:

如果readIndex等于0,直接返回

如果readIndex和writeIndex不相等,首先调用setBytes(int index, ByteBuf src, int srcIndex, int length)方法进行字节数组的复制,

然后重新设置markReadIndex、markWriteIndex、readIndex和writeIndex

如果readIndex等于writeIndex,调整markReadIndex和markWriteIndex,不进行字节数组复制,设置readIndex=writeIndex=0

skipBytes

校验跳过的字节长度:如果跳过的字节长度小于0,则抛出IllegalArgumentException异常,如果跳过的字节数大于可读取的字节数,则抛出IndexOutOfBoundsException异常

校验通过之后,readIndex增加跳过的字节长度

AbstractReferenceCountedByteBuf

该类主要是对引用进行计数,类似于JVM内存回收的对象引用计数器,用于跟踪对象的分配和销毁,做自动内存回收。

成员变量

AtomicIntegerFieldUpdater refCntUpdater对象:通过原子的方式对成员变量进行更新操作,实现线程安全,消除锁。

volatile int refCnt:用于跟踪对象的引用次数,使用volatile是为了解决多线程并发访问的可见性问题。

对象引用计数器

每调用retain()方法一次,引用计数器就会加1,但加完之后会对数据进行校验,具体的校验内容如下:

如果加1之前的引用次数小于等于0或者原来的引用次数 + 增加的次数 < 原来的引用次数,则需要还原这次引用计数器增加操作,并且抛出IllegalReferenceCountException异常

UnpooledHeapByteBuf

UnpooledHeapByteBuf是基于堆内存分配的字节缓冲区,每次I/O读写都会创建一个新的UnpooledHeapByteBuf。

成员变量

ByteBufAllocator alloc:用于UnpooledHeapByteBuf的内存分配

byte[] array:缓冲区数组,此处也可用ByteBuffer,但是用byte数组的原因是提升性能和更加便捷的进行位操作

ByteBuffer tmpNioBuf:用于实现Netty的ByteBuf到JDK NIO ByteBuffer的转换

动态扩展缓冲区

校验新容量:如果新容量小于0或者新容量大于最大容量,抛出IllegalArgumentException异常,否则校验通过

如果新容量大于旧容量,使用new byte[newCapacity]创建新的缓冲数组,然后通过System.arraycopy进行复制,将旧的缓冲区内容拷贝到新的缓冲区中,最后在ByteBuf中替换旧的数组,并且将原来的ByteBuffer tmpNioBuf置为空

如果新容量小于旧容量,使用new byte[newCapacity]创建新的缓冲数组,如果读索引小于新容量(如果写索引大于新容量,将写索引直接置为新容量),然后通过System.arraycopy将当前可读的缓冲区内容复制到新的byte数组,如果读索引大于新容量,说明没有可以拷贝的缓冲区,直接将读写索引置为新容量,并且使用新的byte数组替换原来的字节数组

字节数组复制

setBytes(int index, byte[] src, int srcIndex, int length)

首先是合法性校验,先是校验index,length,如果这两个值有小于0,或者相加小于0,或者两个相加大于ByteBuf的容量,则抛出IndexOutOfBoundsException异常,接着校验被复制的数组的长度和索引问题(srcIndex、length),如果srcIndex、length小于0,或者两个相加小于0,或者两个相加超过了src字节数组的容量,也抛出IndexOutOfBoundsException异常

校验通过之后,使用System.arraycopy方法进行字节数组的拷贝

ByteBuf以get和set开头读写缓冲区的方法不会修改读写索引

转换成JDK ByteBuffer

由于UnpooledHeapByteBuf缓冲区采用了byte数组实现,同样的ByteBuffer底层也是用了byte数组实现,同时ByteBuffer还提供了wrap方法,

直接将字节数组转换成ByteBuffer,最后调用slice方法。由于每次调用都会创建一个新的ByteBuffer,因此起不到重用缓冲区内容的效果。

子类实现相关的方法

hasArray():是否支持数组,判断缓冲区的实现是否基于字节数组

array():如果缓冲区的实现基于字节数组,返回字节数组

PooledByteBuf

PoolArena

Arena是指一块区域,在内存管理中,Memory Arena指内存中的一大块连续的区域,PoolArena是Netty的内存池实现类。

为了集中管理内存的分配和释放,同时提高分配和释放内存的性能,框架会预先申请一大块内存,然后通过提供相应的分配和释放接口来使用内存。由于不再使用系统调用来申请和释放内存,

应用或者系统的性能大大提高。预先申请的那一大块内存称之为Memory Arena。

Netty的PoolArena是由多个Chunk组成的大块内存区域,每个Chunk由一个或者多个Page组成。因此,对内存的组织管理主要集中在如何组织管理Chunk和Page。

PoolChunk

Chunk主要用来组织和管理多个Page的内存分配。Netty中,Chunk中的Page被构造成一棵二叉树。

每一个Page可以成为一个节点,第一层的Page节点用来分配所有Page需要的内存。每个节点记录了自己在Memory Arena中的偏移地址,当一个节点代表的内存区域被分配出去以后,

该节点会被标记为已分配,从这个节点往下的所有节点在后面的内存分配请求中都会被忽略。

在内存分配查找节点时,对树的遍历采用深度优先的算法,但在选择在哪个子节点继续遍历时则是随机的,并不总是访问左边的子节点。

PoolSubpage

对于小于一个Page的内存,Netty在Page中完成分配。每个Page会被切分成大小相等的多个存储块,存储块的大小由第一次申请的内存块大小决定。

一个Page只能用于分配与第一次申请时大小相同的内存。

Page中存储区域的使用状态通过一个long数组来维护,数组中每个long的每一位表示一个块存储区域的占用情况:0表示未占用,1表示已占用。

内存回收策略

Chunk和Page都通过状态位来标识内存是否可用,不同的是Chunk通过在二叉树上对节点进行标识实现,Page是通过维护块的使用状态标识来实现。

PooledDirectByteBuf

PooledDirectByteBuf基于内存池实现。

创建字节缓冲区实例

新创建PooledDirectByteBuf对象不能直接new,而是从内存池Recycler中获取,然后设置引用计数器的值为1,设置缓冲区的最大空间,

设置读写索引、标记读写索引为0。

复制新的字节缓冲区实例

copy(int index, int length)方法可以复制一个ByteBuf实例,并且与原来的ByteBuf相互独立。

首先校验索引和长度的合法性

校验通过之后,调用PooledByteBufAllocator分配一个新的ByteBuf,最终会调用PooledByteBufAllocator中的newDirectBuffer(int initialCapacity, int maxCapacity)方法进行内存的分配

在newDirectBuffer中,直接从缓存中获取ByteBuf而不是创建一个新的对象

ByteBuf辅助类

ByteBufHolder

ByteBufHolder是BytBuf容器。比如,Http协议的请求消息和应答消息都可以携带消息体,这个消息体在Netty中就是ByteBuf对象。由于不同的协议消息体可以包含不同的

协议字段和功能,因此需要对ByteBuf进行包装和抽象,为了满足这些定制化的需求,Netty抽象出了ByteBufHolder对象。

ByteBufAllocator

ByteBufAllocator是字节缓冲区分配器,按照Netty缓冲区的实现不同可以分为:基于内存池的字节缓冲区分配器和普通的字节缓冲区分配器。

方法名称

返回值说明

功能说明

buffer()

ByteBuf

分配一个字节缓冲区,缓冲区的类型由ByteBufAllocator的实现类决定

buffer(int initialCapacity)

ByteBuf

分配一个初始容量为initialCapacity的字节缓冲区,缓冲区的类型由ByteBufAllocator的实现类决定

buffer(int initialCapacity, int maxCapacity)

ByteBuf

分配一个初始容量为initialCapacity,最大容量为maxCapacity的字节缓冲区,缓冲区的类型由ByteBufAllocator的实现类决定

ioBuffer(int initialCapacity, int maxCapacity)

ByteBuf

分配一个初始容量为initialCapacity,最大容量为maxCapacity的Direct Buffer,Direct Buffer I/O性能高

heapBuffer(int initialCapacity, int maxCapacity)

ByteBuf

分配一个初始容量为initialCapacity,最大容量为maxCapacity的Heap Buffer

directBuffer(int initialCapacity, int maxCapacity)

ByteBuf

分配一个初始容量为initialCapacity,最大容量为maxCapacity的Direct Buffer

compositeBuffer(int maxNumComponents)

CompositeByteBuf

分配一个最多包含maxNumComponents个缓冲区的复合缓冲区,缓冲区的类型由ByteBufAllocator的实现类决定

isDirectBufferPooled()

boolean

是否使用了直接内存池

calculateNewCapacity(int minNewCapacity, int maxCapacity)

int

动态扩容时计算新容量

CompositeByteBuf

CompositeByteBuf允许将多个ByteBuf的实例组装到一起。

CompositeByteBuf定义了一个Component类型的集合,Component实际上是ByteBuf的包装实现类,它聚合了ByteBuf对象,维护ByteBuf在集合中的位置偏移量等信息。

CompositeByteBuf支持动态增加(addComponent(ByteBuf buffer))和删除(removeComponent(int cIndex))ByteBuf,增加或删除ByteBuf之后,

需要更新各个ByteBuf的索引偏移量。

ByteBufUtil

ByteBufUtil提供了大量的静态方法来操作ByteBuf。列举三个:

ByteBuf encodeString(ByteBufAllocator alloc, CharBuffer src, Charset charset):对需要编码的字符串src按照指定的字符集charset进行编码,利用指定的ByteBufAllocator生成一个ByteBuf

decodeString(ByteBuf src, int readerIndex, int len, Charset charset):从指定索引readIndex开始往后len个字节长度,对ByteBuf对象src按照指定的字符集charset进行解码

hexDump(ByteBuf buffer):将ByteBuf对象的参数内容以十六进制的格式输出

bytebuf池_Netty ByteBuf相关推荐

  1. bytebuf池_Netty ByteBuf原理剖析

    缓冲区在计算机世界中随处可见,内存中的多级缓冲区,io设备的缓冲区等等,还有我们经常用的内存队列,分布式队列等等.缓冲区,平衡了数据产生方和数据消费方的处理效率差异,提高了数据处理性能. JDK为了解 ...

  2. bytebuf池_Netty默认的Bytebuf是堆内还是堆外?池化or非池化?

    开篇 Netty的ByteBuf有从不同角度有如下2个分类,4种组合! 堆外内存和堆内内存 池化和非池化 我们在利用Netty做底层通信框架的时候,会默认给我们的到底是哪一种组合了? 分析 池化分析 ...

  3. bytebuf池_netty中的ByteBuf

    网络数据的基本单位总是字节.Java NIO 提供了 ByteBuffer 作为它 的字节容器,但是这个类使用起来过于复杂,而且也有些繁琐. Netty 的 ByteBuffer 替代品是 ByteB ...

  4. bytebuf池_Netty篇:ByteBuf之内存池源码分析

    Netty的内存池的实现有些复杂,使用了大量的位运算,晦涩难懂,不过万能的博客上好多大神已经介绍的非常详细,推荐四篇很详细很棒的源码分析的文章链接,本文根据自己的理解顺一下思路,内容主要也是出自以下四 ...

  5. bytebuf池_Netty java从ByteBuf获取数据

    ByteBuf buf = ... byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); 如果您不希望readerIn ...

  6. bytebuf池_netty源码解析(4.0)-26 ByteBuf内存池:PoolArena-PoolSubpage

    PoolChunk用来分配大于或等于一个page的内存,如果需要小于一个page的内存,需要先从PoolChunk中分配一个page,然后再把一个page切割成多个子页-subpage,最后把内存以s ...

  7. bytebuf池_图文分析ByteBuf是什么

    ByteBuf是什么 ByteBuf是Netty中非常重要的一个组件,他就像物流公司的运输工具:卡车,火车,甚至是飞机.而物流公司靠什么盈利,就是靠运输货物,可想而知ByteBuf在Netty中是多么 ...

  8. java 检查bytebuf长度_Netty实战五之ByteBuf

    网络数据的基本单位总是字节,Java NIO 提供了ByteBuffer作为它的字节容器,但是其过于复杂且繁琐. Netty的ByteBuffer替代品是ByteBuf,一个强大的实现,即解决了JDK ...

  9. Netty 学习之旅:ByteBuf 篇之 ByteBuf 内部结构与 API 学习

    1.原生 ByteBuffer.ByteBuf 内部结构设计 首先我们来回顾一下 java.nio.ByteBuffe r的内部结构设计. ByteBuffer 内部持有一个 byte[] bt, 再 ...

最新文章

  1. CLR Profiler 性能分析工具 (转)
  2. Anaconda 默认环境
  3. MIT自然语言处理第三讲:概率语言模型
  4. 推荐一款基于web的Unix系统管理工具webmin
  5. 具有Eclipse和嵌入式JBoss HornetQ Server的简单JMS 1.1生产者和使用者示例
  6. hsql导入mysql_转-jira从HSQL迁移到MYSQL
  7. 为你的企业建立竞争情报系统 (转)
  8. Amazon DynamoDB应用—为table指定Provisioned Throughput
  9. TCP快速恢复算法PRR
  10. Spark核心开发者:性能超Hadoop百倍,算法实现仅有其1/10或1/100
  11. 【sql】178. 分数排名---两种解决方法,是否使用group by分组成为重点,建议使用分组!!!
  12. java fianlly_Java冷知识:finally中的代码一定会执行吗?
  13. cytoscape插件下载_Cytoscape软件下载
  14. 华为fusion computer虚拟机存储数据恢复
  15. 简易超声波雷达的arduino实现
  16. 解决WPS文档打开显示空白该怎么解决
  17. 如何使用博客进行营销,博客营销的五个步骤
  18. 计算机考研要准备哪些准备,大三 计算机科学与技术 要考研 要做哪些准备了?...
  19. PAT乙级真题 1075 链表元素分类 C++实现(测试点5:用map会超时)
  20. ☘gMIS吉密斯i18n国际化多语言更新

热门文章

  1. css怎么改颜色不换行,css a不换行怎么设置
  2. Mini_Web开发
  3. sqli-labs-Basic
  4. FP6296 内置10A mos大电流升压芯片
  5. 集装箱船受压,中国货源网没有达到预期
  6. 一招看穿一切旁氏骗局
  7. php中 如何制作报名表,如何在问卷网上制作一份报名表?
  8. XCode各版本对应的Mac OS操作系统版本
  9. 液晶显示屏工作原理?
  10. python os.walk遍历目录_python中os.walk()遍历目录中所有文件