Netty篇:ByteBuf部分源码分析

缓冲区(Buffer)


     缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对输入/输出(I/O)的数据作临时存储,这部分预留的内存空间就叫做缓冲区:


     使用缓冲区可以减少实际的物理读写次数,而且缓冲区在创建时就被分配内存,这块内存区域一直被重用,可以减少动态分配和回收内存的次数




ByteBuffer


     常用缓冲区就是JDK NIO类库提供的java.nio.Buffer,7种数据类型(Boolean除外)均有自己的缓冲区实现,NIO编程主要用ByteBuffer,ByteBuffer完全可以满足NIO编程的需要,但是由于NIO编程的复杂性,ByteBuffer也有局限性,主要缺点有:

     (1)ByteBuffer长度固定,一旦分配完成,它的容量不能动态扩展和收缩,当需要编码的POJO对象大于ByteBuffer的容量时,会发生索引越界异常



     (2)ByteBuffer只有一个标识位置的指针position,读取的时候需要手工调用flip()和rewind()等



     (3)ByteBuffer的API功能有限,一些高级和实用的特性它不支持




BufferBuf



     BufferBuf是个抽象类,其子类非常多。


     从内存分派角度讲,ByteBuf可以分为两类:


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


     (2)DirectByteBuf:直接缓冲区的字节数组位于JVM堆外的NATIVE堆,由操作系统管理申请和释放,而DirectByteBuf的引用由JVM管理。直接缓冲区由操作系统管理,一方面,申请和释放效率都低于堆缓冲区,另一方面,却可以大大提高IO效率。由于进行IO操作时,常规下用户空间的数据(JAVA即堆缓冲区)需要拷贝到内核空间(直接缓冲区),然后内核空间写到网络SOCKET或者文件中。如果在用户空间取得直接缓冲区,可直接向内核空间写数据,减少了一次拷贝,可大大提高IO效率,这也是常说的零拷贝。


     从内存回收角度讲,ByteBuf可以分为两类:


      (1)UnpooledByteBUf:不使用对象池的缓冲区,不需要创建大量缓冲区对象时建议使用该类缓冲区


      (2)PooledByteBuf:对象缓冲区,当对象释放后会归还给对象池,所以可循环利用,提升内存的使用效率,降低由于高负载导致的频繁GC,当需要大量且频繁创建缓冲区时,建议使用该类缓冲区

在这里插入图片描述




AbstractByteBuf


      AbstractByteBuf继承自ByteBuf,ByteBuf的一些公共属性和功能会在AbstractByteBuf中实现,比如读写索引以及标记索引的维护、容量扩增以及废弃字节丢弃等公共功能,例如:


定义读写索引以及标记索引
在这里插入图片描述


读操作
在这里插入图片描述
在这里插入图片描述

      首先对缓冲区的可用空间进行校验,从当前读索引开始,复制length个字节到目标byte数组中。由于不同的子类复制操作的技术实现细节不同,因为该方法由子类实现。如果读取成功,需要对读索引进行递增:readerIndex+=length


写操作
在这里插入图片描述
在这里插入图片描述

      首先对写入字节数字的长度进行合法性校验,如果当前写入的字节数组长度虽然大于目前ByteBuf的可写字节数,当时通过自身的动态扩展可以满足新的写入请求,则进行动态扩展,计算扩容代码如下:

在这里插入图片描述

      如果扩容后的新容量小于阈值,则以64为计数进行倍增,知道被增厚的结果大于或等于到需要的容量值。

      采用倍增或者步进算法的原因:如果以minNewCapacity作为目标容量,则本次扩容后的可写字节数刚好够本子写入使用。写入完成后,可写字节数变为0,下次做写入操作是,仍需扩容,由于扩容需要进行内存复制,频繁的内存复制会导致性能下降。

      采用先倍增后步进的原因:当内存比较小的情况下,倍增操作并不会带来太多的内存浪费,当内存增长到一定的阈值后,在进行倍增就可能带来额外的内存浪费。

      重新计算完动态扩张后的目标容量后,需要重新创建新的缓冲区,将原缓冲区的内容复制到新创建的ByteBuf中,最后设置读写索引和mark标签等,由于子类对应不同的复制操作,所以抽象方法,由子类实现。


重用缓冲区
在这里插入图片描述
在这里插入图片描述

      首先对读索引进行判断,如果为0则表示没有可重用的缓冲区,直接返回。如果读索引大于0且读索引不等于写索引,说明缓冲区中有已经读取过被丢弃的缓冲区,也有尚未读取的缓冲区,。调用setBytes方法进行字节数组复制。将尚未读取的字节数复制到缓冲区的起始位置,然后重新设置读写索引,读索引设置为0,写索引设置为之前的写索引减去读索引。

在这里插入图片描述

      在设置读写索引的同时,需要调整markedReaderIndex和markedWriterIndex。

      如果readerIndex等于writeIndex则说明没有可读的字节数组,就不需要进行内存复制,直接调整mark,将读写索引设置为0进而可完成缓冲区的重用。




AbstractReferenceCountedByteBuf


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

在这里插入图片描述


      ReferenceCountUpdater类的updater()和unsafeOffset()方法是抽象方法,需要具体的子类提供实现。AtomicIntegerFieldUpdater是JDK提供的一个可以通过原子更新的方式修改指定字段的工具。

      ReferenceCountUpdater类的功能是可以通过CAS的方式直接修改某个类的一个字段的值。




ReferenceCountUpdater#retain 增加引用计数的值:


在这里插入图片描述

      委派update()类对refCnt用CAS操作加2(refCnt是偶数则表示当前缓冲区的状态为正常状态,如果refCnt是奇数则表示缓冲区的状态为待销毁状态。缓冲区引用计数的真实值为refCnt/2),然后做缓冲区释放和溢出校验。




ReferenceCountUpdater#release 减少引用计数的值:


在这里插入图片描述

      获取instance实例中保存的rawCnt值,并计算出真实的refCnt值,即realCnt,如果要减少的引用值和真实的refCnt值相同,也即需要释放缓冲区对象,则调用tryFinalRelease0方法将refCnt的数值修改为1(只要修改为奇数即可),如果要减少的引用值小于真实的refCnt值,则通过cas修改refCnt的值,调用Thread.yield()方法释放出CPU的执行权,因为修改引用计数的逻辑在整个系统逻辑的优先级并不高,所以让出执行权有利于提高高并发下的系统吞吐量。




参考:


《Netty权威指南》

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350