ByteBuf是顶层的抽象类,定义了用于传输数据的ByteBuf需要的方法和属性。

AbstractByteBuf

  直接继承ByteBuf,一些公共属性和方法的公共逻辑会在这里定义。例如虽然不同性质的ByteBuf底层实现不同如堆Buffer和直接Buffer,但在进行数据写入的时候都要检查并移动readIndex,因此诸如检查并移动readIndex的方法就抽取并定义在AbstractByteBuf方法中。

公共成员变量

  分为两大类,一是读写索引和mark索引,一个是leakDetector,用于检查是否存在内存泄露,该成员变量是static意味着所有ByteBuf的实例共享同一个leakDetector,单例设计模式处处可见。

static final ResourceLeakDetector<ByteBuf> leakDetector;
int readerIndex;
int writerIndex;
private int markedReaderIndex;
private int markedWriterIndex;
private int maxCapacity;

读操作簇

  以readBytes为例,在AbstractByteBuf里主要做了两件事情

  • 合法性检查
  • 调用getBytes,之所以用调用是因为getBytes会因为子类的不同实现而有所区别
  • 索引移动
    public ByteBuf readBytes(byte[] dst, int dstIndex, int length) {
this.checkReadableBytes(length);
this.getBytes(this.readerIndex, dst, dstIndex, length);
this.readerIndex += length;
return this;
}

  

写操作簇

  同样做了三件事情

  • 合法性检查
  • 调用setBytes
  • 索引移动  
    public ByteBuf writeBytes(ByteBuf src, int srcIndex, int length) {
this.ensureWritable(length);
this.setBytes(this.writerIndex, src, srcIndex, length);
this.writerIndex += length;
return this;
}

  在ensureWritable中封装了扩容逻辑,这也是ByteBuf比NIO中的ByteBuffer优秀的地方。首先检查传入的length是否小于零,小于零则抛出异常,否则执行ensureWritable0,Netty给方法起的名字总是这么随意……

    public ByteBuf ensureWritable(int minWritableBytes) {
if (minWritableBytes < 0) {
throw new IllegalArgumentException(String.format("minWritableBytes: %d (expected: >= 0)", minWritableBytes));
} else {
this.ensureWritable0(minWritableBytes);
return this;
}
}
  • 如果当前期望写入的数据长度(minWritableByte)大于此时ByteBuf的长度(writableBytes)且大于剩余的最大容量(this.MaxCapacity-this.writerIndex),抛出异常。ByteBuf真的不能写了。
  • 如果当前期望写入的数据长度(minWritableByte)大于此时ByteBuf的长度(writableBytes),但小于剩余的最大容量(this.MaxCapacity-this.writerIndex),调用calculateNewCapacity扩容,返回扩容后的大小(newCapacity),并重新设置当前ByteBuf(this.capacity(newCapacity))。
  • 如果当前期望写入的数据长度(minWritableByte)小于此时ByteBuf的长度,啥也不干。
final void ensureWritable0(int minWritableBytes) {
this.ensureAccessible();
if (minWritableBytes > this.writableBytes()) {
if (minWritableBytes > this.maxCapacity - this.writerIndex) {
throw new IndexOutOfBoundsException(String.format("writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s", this.writerIndex, minWritableBytes, this.maxCapacity, this));
} else {
int newCapacity = this.alloc().calculateNewCapacity(this.writerIndex + minWritableBytes, this.maxCapacity);
this.capacity(newCapacity);
}
}
}

  calculateNewCapacity传入两个参数:能够完成这里写入所需要的最小容量(minNewCapacity),当前支持的最大容量(maxCapacity)。

  • 首先合法性检查,minNewCapacity<0或者minNewCapacity>maxCapacity抛出异常。
  • 如果需要的容量等于4MB(minNewCapacity==4194304),那么把4MB作为扩容后的容量并返回。
  • 如果需要的容量大于4MB,首先找到一个小于等于4MB整数倍的容量(newCapacity),如果newCapacity加上4MB大于maxCapacity则返回maxCapacity,否则返回newCapacity加4MB,保证扩容后的容量永远是4MB的整数倍,这种思想和HashMap扩容很接近,有人把这种扩容称为步进4MB扩容方式。
  • 如果需要的容量小于4MB,从64开始倍增,直到大于等于需要的容量。
public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {
if (minNewCapacity < 0) {
throw new IllegalArgumentException("minNewCapacity: " + minNewCapacity + " (expected: 0+)");
} else if (minNewCapacity > maxCapacity) {
throw new IllegalArgumentException(String.format("minNewCapacity: %d (expected: not greater than maxCapacity(%d)", minNewCapacity, maxCapacity));
} else {
int threshold = 4194304;
if (minNewCapacity == 4194304) {
return 4194304;
} else {
int newCapacity;
if (minNewCapacity > 4194304) {
newCapacity = minNewCapacity / 4194304 * 4194304;
if (newCapacity > maxCapacity - 4194304) {
newCapacity = maxCapacity;
} else {
newCapacity += 4194304;
} return newCapacity;
} else {
for(newCapacity = 64; newCapacity < minNewCapacity; newCapacity <<= 1) {
} return Math.min(newCapacity, maxCapacity);
}
}
}
}

  这里应该还有一个复制的操作,但是在这个抽象类里没有找到,应该在具体的实现类中。

重用缓冲区

  把已读的部分[0,readIndex]重用起来。

  • 如果readIndex==0,没有可以重用的区域。
  • 如果readIndex!=0且readIndex!=writeIndex,说明在0~readIndex区间内的内存可以重用,调用setBytes,把尚未读取的内容复制到缓冲区的开始,然后把writerIndex向前挪(this.writerIndex-=this.readerIndex),重设读索引为0。
  • 如果readIndex!=0且readIndex==writeIndex,说明有可以重用的内存,且不存在还没有读取的数据,直接重设读写索引为0(readIndex=writeIndex=0)
    public ByteBuf discardReadBytes() {
this.ensureAccessible();
if (this.readerIndex == 0) {
return this;
} else {
if (this.readerIndex != this.writerIndex) {
this.setBytes(0, this, this.readerIndex, this.writerIndex - this.readerIndex);
this.writerIndex -= this.readerIndex;
this.adjustMarkers(this.readerIndex);
this.readerIndex = 0;
} else {
this.adjustMarkers(this.readerIndex);
this.writerIndex = this.readerIndex = 0;
} return this;
}
}

AbstractReferenceCountedByteBuf

  直接继承自AbstractByteBuf,添加了引用计数的功能,类似于JVM垃圾回收中的引用计数法,当一个ByteByf对象引用计数为1时,代表该ByteBuf没有有效引用,可以被回收。

属性

  • refCntUpdater,一个Atomic的对象,使用refCntUpdater的CAS方法线程安全的修改refCnt,实现无锁化,提高效率。
  • refCnt,volatile类型的变量,记录当前ByteBuf的引用次数。初始值为1,每次用新的引用增加1,失去一个引用减1,当最终再次回到1的时候释放ByteBuf
private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater =
AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt"); private volatile int refCnt = 1;

retain

  每调用一次retain方法refCnt就会线程安全的增加一,retain主要调用了retain0方法,netty方法的名字还是这样随意。熟悉的JUC的味道,熟悉的for(;;)写法,做了两件事情。

  • 合法性检查。nextCnt<=increment就会抛出异常,这里很有意思,分两个情况来看:nextCnt<increment,nextCnt=increment+refCnt,increment是一个大于零的数,只有在溢出的情况下nextCnt才会小于refCnt,所以小于号是来避免溢出的发生。nextCnt=increment,increment一定是大于零的,那么只有refCnt=0的时候才会发生nextCnt=increment,所以在netty看来refCnt=0是一个异常情况,这种异常情况只会发生在错误调用release的情况下才会发生。
  • 自旋CAS修改refCnt。
private ByteBuf retain0(int increment) {
for (;;) {
int refCnt = this.refCnt;
final int nextCnt = refCnt + increment; // Ensure we not resurrect (which means the refCnt was 0) and also that we encountered an overflow.
if (nextCnt <= increment) {
throw new IllegalReferenceCountException(refCnt, increment);
}
if (refCntUpdater.compareAndSet(this, refCnt, nextCnt)) {
break;
}
}
return this;
}

release

  每调用一次release,refCnt会线程安全的减少一,当减少到refCnt==decrement时,调用deallocate方法回收ByteBuf内存,deallocate需要由具体的子类重写。

private boolean release0(int decrement) {
for (;;) {
int refCnt = this.refCnt;
if (refCnt < decrement) {
throw new IllegalReferenceCountException(refCnt, -decrement);
} if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) {
if (refCnt == decrement) {
deallocate();
return true;
}
return false;
}
}
}

UnpooledHeapByteBuf

  • unpooled,非池化,每次使用ByteBuf都需要新申请一个ByteBuf,相较于Pooled效率较低,但使用起来较为简单。
  • Heap,分配在堆上。

属性

  • alloc,聚合了一个ByteBufAllocator,用于分配内存。
  • array,缓冲区。
  • tmpNioBuf,用于实现ByteBuf到NIO中的ByteBuffer转换。
    private final ByteBufAllocator alloc;
byte[] array;
private ByteBuffer tmpNioBuf;

capacity

  用于调整ByteBuf的长度到指定长度,与写方法簇中的动态调整不同,后者的调用对使用者是透明的,而用户可以主动的调用capacity方法调整长度。如果newCapacity>oldCapacity,那么就在ByteBuf里添加一unspecified数据,如果newCapacity<oldCapacity那么从ByteBuf里删除一些数据。

  • 如果newCapacity>oldCapacity,分配一个新的byte数组(allocateArray),把旧的数组复制过去(arraycopy),让属性中的arr指向新的数组,释放就的数组对象。
  • 如果newCapacity<oldCapacity,需要截取。如果readIndex<newCapacity<writeCapacity,则把writeIndex设置为新的容量。如果newCapacity<readerIndex,说明没有可读的字节数组需要复制。
    public ByteBuf capacity(int newCapacity) {
checkNewCapacity(newCapacity); int oldCapacity = array.length;
byte[] oldArray = array;
if (newCapacity > oldCapacity) {
byte[] newArray = allocateArray(newCapacity);
System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
setArray(newArray);
freeArray(oldArray);
} else if (newCapacity < oldCapacity) {
byte[] newArray = allocateArray(newCapacity);
int readerIndex = readerIndex();
if (readerIndex < newCapacity) {
int writerIndex = writerIndex();
if (writerIndex > newCapacity) {
writerIndex(writerIndex = newCapacity);
}
System.arraycopy(oldArray, readerIndex, newArray, readerIndex, writerIndex - readerIndex);
} else {
setIndex(newCapacity, newCapacity);
}
setArray(newArray);
freeArray(oldArray);
}
return this;
}

ByteBuf源码的更多相关文章

  1. Netty学习篇⑥--ByteBuf源码分析

    什么是ByteBuf? ByteBuf在Netty中充当着非常重要的角色:它是在数据传输中负责装载字节数据的一个容器;其内部结构和数组类似,初始化默认长度为256,默认最大长度为Integer.MAX ...

  2. Netty ByteBuf源码分析

    Netty的ByteBuf是JDK中ByteBuffer的升级版,提供了NIO buffer和byte数组的抽象视图. ByteBuf的主要类集成关系: (图片来自Netty权威指南,图中有一个画错的 ...

  3. Netty学习(三)高性能之ByteBuf源码解析

    原文链接: https://juejin.im/post/5db8ea506fb9a02061399ab3 Netty 的 ByteBuf 类型 Pooled(池化).Unpooled(非池化) Di ...

  4. netty(六) buffer 源码分析

    问题 : netty的 ByteBuff 和传统的ByteBuff的区别是什么? HeapByteBuf 和 DirectByteBuf 的区别 ? HeapByteBuf : 使用堆内存,缺点 ,s ...

  5. Netty 5.0源码分析-ByteBuf

    1. 概念 Java NIO API自带的缓冲区类功能相当有限,没有经过优化,使用JDK的ByteBuffer操作更复杂.故而Netty的作者Trustin Lee为了实现高效率的网络传输,重新造轮子 ...

  6. Netty(7)源码-ByteBuf

    一.ByteBuf工作原理 1. ByteBuf是ByteBuffer的升级版: jdk中常用的是ByteBuffer,从功能角度上,ByteBuffer可以完全满足需要,但是有以下缺点: ByteB ...

  7. Netty源码分析第5章(ByteBuf)---->第1节: AbstractByteBuf

    Netty源码分析第五章: ByteBuf 概述: 熟悉Nio的小伙伴应该对jdk底层byteBuffer不会陌生, 也就是字节缓冲区, 主要用于对网络底层io进行读写, 当channel中有数据时, ...

  8. Netty源码分析第5章(ByteBuf)---->第2节: ByteBuf的分类

    Netty源码分析第五章: ByteBuf 第二节: ByteBuf的分类 上一小节简单介绍了AbstractByteBuf这个抽象类, 这一小节对其子类的分类做一个简单的介绍 ByteBuf根据不同 ...

  9. Netty源码分析第5章(ByteBuf)---->第3节: 缓冲区分配器

    Netty源码分析第五章: ByteBuf 第三节: 缓冲区分配器 缓冲区分配器, 顾明思议就是分配缓冲区的工具, 在netty中, 缓冲区分配器的顶级抽象是接口ByteBufAllocator, 里 ...

随机推荐

  1. GoCN每日新闻(2019-10-09)

    GoCN每日新闻(2019-10-09) GoCN每日新闻(2019-10-09) 1. 我们如何将服务延迟减少了98% https://blog.gojekengineering.com/the-n ...

  2. CSS系列之后代选择器、子选择器和相邻兄弟选择器

    后代选择器比子选择器的范围大,包含子选择器,且包含子选择器的“子孙”选择器,后代选择器使用"空格"符号间隔选择器 子选择器:子选择器只是父选择器的一级子元素,使用"> ...

  3. pdfBox 解析 pdf文件

    Spting boot 项目 1.添加依赖 <dependency> <groupId>org.apache.pdfbox</groupId> <artifa ...

  4. [Beta]第九次 Scrum Meeting

    [Beta]第九次 Scrum Meeting 写在前面 会议时间 会议时长 会议地点 2019/5/19 21:20 20min 大运村公寓6F寝室 附Github仓库:WEDO 例会照片 (一人回 ...

  5. 关于资源获取(请把https改为http)

    所有demo以及资源获取,请把https改为http.

  6. 自动化远程部署shell脚本

    历史原因,有一段时间,项目开发采用一种模式:项目开发及代码版本管理在外网,而主要测试在内网.所以为了同步开发进度,每天会将所有服务在外网jenkins上打包好,然后将服务jar包拷进内网,由于内网服务 ...

  7. 用于抓取vijos所有题目信息的node.js脚本

    代码如下: var superagent = require('superagent'); var fs = require('fs'); /* fetch_vijos_problems 这个脚本用于 ...

  8. 浏览器中开发人员工具快速找到dom元素绑定那些JS事件

    在web开发过程中难免会遇到让程序员去修改一些js代码东西,例如js的ajax和php等语言的交互等,在这其中你不得不了解点js的事件触发,且随着js的盛行各种插件的事件让程序员眼花缭乱,所以借助一个 ...

  9. LeetCode:字符串相加【415】

    LeetCode:字符串相加[415] 题目描述 给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和. 注意: num1 和num2 的长度都小于 5100.num1 和num2 都只 ...

  10. Kubernetes 使用 ingress 配置 https 集群(十五)

    目录 一.背景 1.1 需求 1.2 Ingress 1.3 环境介绍 二.安装部署 2.1.创建后端 Pod 应用 2.2 创建后端 Pod Service 2.3.创建 ingress 资源 2. ...