ByteBuf学习笔记
一、ByteBuf的主要类继承关系:
二、ByteBuf容量动态扩展:
ByteBuf允许使用者指定最大容量,在容量范围内可以先分配较小的初始容量,如果不够,可以动态扩展,以达到功能和性能的最优组合。
动态扩容的核心方法就是AbstractByteBufAllocator的**calculateNewCapacity()**方法,该方法入参minNewCapacity=writerIndex + minWritableBytes,为写索引加上当前待写入的长度,表示当前写入时所需的最小容量,maxCapacity为缓存能分配的最大容量
扩容时,设置了阀值threshold=4M(也称步进值):
扩容策略:当所需内存比较小的时候,采用倍增策略,当所需内存较大时,采用步进策略,比如所需10M,进行扩容时,若采用倍增:10*2=20M,则浪费了10M,但是步进策略后为14M,减少了内存浪费。
- 当所需最小容量大于步进值时:
如果当前所需最小容量minNewCapacity+步进值大于最大容量,那么就返回最大容量maxCapacity,如果小于最大容量,就以4M进行步进 - 当所需最小容量小于步进值时:
扩容的容量newCapacity以初始值64开始,以乘2递增,直到扩容的容量newCapacity大于等于当前写入时所需的最小容量。
@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));
}
final int threshold = 1048576 * 4; // 4 MiB page
if (minNewCapacity == threshold) {
return threshold;
}
// 如果当前写入需要的最小容量超过了阀值,就以阀值进行步进
if (minNewCapacity > threshold) {
int newCapacity = minNewCapacity / threshold * threshold;
if (newCapacity > maxCapacity - threshold) { // 如果以阀值进行步进,超过了最大容量,就直接返回最大容量
newCapacity = maxCapacity;
} else {
newCapacity += threshold; //否则新容量就是当前所需最小容量加上阀值
}
return newCapacity;
}
// 以初始值为64开始,每次乘2,进行递增
int newCapacity = 64;
while (newCapacity < minNewCapacity) {
newCapacity <<= 1;
}
return Math.min(newCapacity, maxCapacity);
}
三、ByteBuf相关方法:
1、readBytes:
AbstractByteBuf中实现了父类的readBytes方法,但是readBytes方法调用的getBytes方法是交由子类去实现
public abstract ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length);
对于Heap 类型的ByteBuf,数据存入byte数组,每次读取的时候,会进行数组复制:其getByte方法如下:
@Override
public final ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
checkDstIndex(index, length, dstIndex, dst.length);
System.arraycopy(memory, idx(index), dst, dstIndex, length);
return this;
}
数组复制调用的是java native 方法:System.arraycopy(src, srcPos, dst, dstIndex, length)速度非常快,PS: src 源数组,srcPos源数组开始复制的位置,dst目的数组,dstIndex数组复制的位置,length复制内容长度
2、discardReadBytes:
该方法是丢弃ByteBuf中已读取的字节,调用setBytes方法,将readerIndex与writerIndex 之间未读取的数据,复制到缓冲区的起始位置。
setBytes(0, this, readerIndex, writerIndex - readerIndex);
并且读索引readerIndex调整到起始位置0,写索引位置writerIndex为写索引位置减去之前读索引位置。
并且要调整markedReaderIndex,markedWriterIndex的位置,如果markedReaderIndex,markedWriterIndex小于需要的减少量decrement=readerIndex,就置为0,如果大于,则将值更新为减去decrement后的值。
四、ByteBuf引用计数器AbstractReferenceCountedByteBuf:
该类中重要成员变量参数:
【netty5 与netty4不同,多了REFTCNT_FIELD_OFFSET字段:用于标识refCnt在AbstractReferenceCountedByteBuf对象中内存地址】
private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater; //通过AtomicIntegerFieldUpdater来对 refCnt字段原子性更新
private volatile int refCnt = 1; // 用于跟踪对象的引用次数
引用计数refCnt增加:
private ByteBuf retain0(int increment) { // // 通常decrement 为1
for (;;) {
int refCnt = this.refCnt;
final int nextCnt = refCnt + increment;
if (nextCnt <= increment) {
throw new IllegalReferenceCountException(refCnt, increment); // 这种情况下,说明refCnt 为0,nextCnt 才会等于increment
}
if (refCntUpdater.compareAndSet(this, refCnt, nextCnt)) { // 通过AtomicIntegerFieldUpdater 的CAS操作,来更新引用计数refCnt
break;
}
}
return this;
}
引用计数释放:
private boolean release0(int decrement) { // 通常decrement 为1
for (;;) {
int refCnt = this.refCnt;
if (refCnt < decrement) {
throw new IllegalReferenceCountException(refCnt, -decrement);
}
if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) { // 原子更新引用计数
if (refCnt == decrement) { // 注意当refCnt =1时,表明申请和释放相等,对象不可达,那么该对象需要被释放回收
deallocate(); // 对象释放方法交由子类实现,因为父类不清楚子类申请的字节缓冲是堆内的内存,还是堆外内存
return true;
}
return false;
}
}
}