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. 【cf比赛练习记录】Codeforces Round #579 (Div. 3)

    思考之后再看题解,是与别人灵魂之间的沟通与碰撞 A. Circle of Students 题意 给出n个数,问它们向左或者向右是否都能成一个环.比如样例5是从1开始向左绕了一圈 [3, 2, 1, ...

  2. Vue2.0 render: h => h(App)的解释

    render: h => h(App)是ES6的写法,其实就是如下内容的简写: render: function (createElement) { return createElement(A ...

  3. Spring Cloud Feign踩坑记录(二)

    注意,以下的Feign遇到的坑,在高版本中有些已经修复. 某些项目由于历史包袱原因,无法进行全面升级,才需要修补这些坑. 1.启动报错:not annotated with HTTP method t ...

  4. 定制比特币btc地址address

    https://99bitcoins.com/how-to-get-a-custom-bitcoin-address/ https://en.bitcoin.it/wiki/Vanitygen htt ...

  5. Cesium学习笔记-工具篇20-PrimitiveTexture自定义渲染-贴图【转】

    前几篇博客我们了解了自定义点.线.面绘制,这篇我们接着学习cesium自定义纹理贴图.我们完成点线面的绘制,只是绘制出了对象的框架,没有逼真的外观.逼真外观是需要设置材质来实现:Material . ...

  6. SpringBoot @Autowired中注入静态方法或者静态变量

    注:用static去定义一个注入的方法或者配置文件值变量,编译时不会有任何异常,运行时会报空指针. Spring官方不推荐此种方法. 原理: https://www.cnblogs.com/chenf ...

  7. [简短问答]C-Lodop中一些测试用的地址

    测试访问:访问http://localhost:8000欢迎页面试试进入欢迎页面http://localhost:8000,点欢迎页面的预览试试 查看下c-lodop启动界面,在设置里查看下当前启动的 ...

  8. 使用Termux,在手机上做nodejs编程,运行nodejs程序。

    如果你是一名nodejs开发者,是否想过以下问题:在手机上运行nodejs程序?用手机当nodejs服务器?在手机上做nodejs编程?YES!使用Termux,以上都可以做到! 下面展示如何实现这个 ...

  9. [LeetCode] 349. Intersection of Two Arrays 两个数组相交

    Given two arrays, write a function to compute their intersection. Example 1: Input: nums1 = [1,2,2,1 ...

  10. [LeetCode] 362. Design Hit Counter 设计点击计数器

    Design a hit counter which counts the number of hits received in the past 5 minutes. Each function a ...