一,概念
Netty提供了ByteBuf代替java NIO的ByteBuffer。
1.ByteBuf的优势:池化,减少了内存复制,GC,提升了效率,复合缓冲区类型,不需要调用flip()进行状态切换,读取和写入所以分开,方法的链式调用,可以进行引用计数,方便重复使用。
2.ByteBuf逻辑部分: 内部是一个字节数组,从逻辑上看,可以分为四个部分,第一个:废弃,以用字节,第二个:可读,有效数据。第三个:可写。第四个:可扩容,
3.ByteBuf的重要属性:分为可读数据和可写数据,使得独写之间没有冲突,所以存在读指针,写指针,最大容量指针
也就是对应上面图中的分界处指针位置。它的具体存放在子类AbstractByteBuf类中。
二,源码分析
1.ByteBuf
它不是接口,二是一个抽象类,提供了很多操控容器的方法,但是它并没有成员字段,只有方法,
方法分类:
1.容量系列:表示ByteBuf的容量,它的值=废弃的字节数+可读字节数+可写字节数
2.写入系列:是否可写写入数据,取得最大的可写入的字节数,写入数据。
3.读取系列:返回Bytebuf是否可读,读取基本数据类型。
2.AbtractByteBuf
继承自ByteBuf,对其进行了一些基本的实现。
重要字段,三个指针:
int readerIndex; //读取位置,指针
int writerIndex; //写入位置,指针
private int markedReaderIndex;
private int markedWriterIndex;
private int maxCapacity; //最大位置,包括了可扩容部分。
mark 用来回滚。
2.1 确保可写的方法
ByteBuf与nio ByteBuffer的不同就是,netty可以动态扩展空间。
final void ensureWritable0(int minWritableBytes) {
final int writerIndex = writerIndex();
final int targetCapacity = writerIndex + minWritableBytes;
if (targetCapacity <= capacity()) {
ensureAccessible();
return;
}
if (checkBounds && targetCapacity > maxCapacity) {
ensureAccessible();
...
}
// Normalize the target capacity to the power of 2.
final int fastWritable = maxFastWritableBytes();
int newCapacity = fastWritable >= minWritableBytes ? writerIndex + fastWritable
: alloc().calculateNewCapacity(targetCapacity, maxCapacity); //扩展空间
// Adjust to the new capacity.
capacity(newCapacity);
}
扩容的机制,算法
位于AbstractByteBufAllocator,实现了Allocator接口,它是ByteBuf的分配器,池化和非池化ByteBuf的父类。
它的第一个参数,表示所需的最小容量,第二个参数是只最大容量(包含第四部分,可扩容容量)
public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {
checkPositiveOrZero(minNewCapacity, "minNewCapacity");
if (minNewCapacity > maxCapacity) {
throw new IllegalArgumentException(String.format(
"minNewCapacity: %d (expected: not greater than maxCapacity(%d)",
minNewCapacity, maxCapacity));
}
final int threshold = CALCULATE_THRESHOLD; // 4 MiB page
//刚好等于,可能不太现实
if (minNewCapacity == threshold) {
return threshold;
}
// If over threshold, do not double but just increase by threshold.
//所需的最小容量,大于4mb,每次增加4mb
if (minNewCapacity > threshold) { //取整,获得现在已经站了多少个完整的单位,再乘
int newCapacity = minNewCapacity / threshold * threshold;
if (newCapacity > maxCapacity - threshold) { //检测是否越界
newCapacity = maxCapacity;
} else { //扩展
newCapacity += threshold;
}
return newCapacity;
}
//没有超过4mb,容量翻倍
// Not over threshold. Double up to 4 MiB, starting from 64.
int newCapacity = 64;
while (newCapacity < minNewCapacity) {
newCapacity <<= 1; //位运算,*2
}
//确保未越界
return Math.min(newCapacity, maxCapacity);
}
总结:当最小所需的容量超过了4mb,则+4mb,如果没有超过,则*2
这些的前提都是不要超过maxCapacity。
2.2 丢弃已读字节discardReadBytes()
public ByteBuf discardReadBytes() {
if (readerIndex == 0) {
ensureAccessible();
return this;
}
if (readerIndex != writerIndex) {
//将readerIndex之后的数据,移动到从0开始
setBytes(0, this, readerIndex, writerIndex - readerIndex);
writerIndex -= readerIndex; //写索引减少readerIndex
adjustMarkers(readerIndex); //标记索引对应调整
readerIndex = 0; //读索引 重置为0
} else { //两个相等,等同clear()操作
ensureAccessible();
adjustMarkers(readerIndex);
writerIndex = readerIndex = 0;
}
return this;
}
2.3 常用数据获取
以getInt()和readInt()为例:
public int getInt(int index) {
checkIndex(index, 4); //索引正确性检测
return _getInt(index); //入口
}
protected abstract int _getInt(int index); //抽象方法,交给子类实现
public int readInt() {
checkReadableBytes0(4);
int v = _getInt(readerIndex); //还是调用get()
readerIndex += 4; //读指针,移动了
return v;
}
可见,get方法指定索引(随机读取,ByteBuffer只支持顺序读取),是不会改变读指针位置的,而readInt()不给位置,所以读指针读取,读取完毕之后,会改变读指针位置。而具体的实现呢,交给具体的ByteBuf实现,我们在下一篇中讲解ByteBuf的创建和释放。
3.AbstractReferenceCountedByteBuf
多了一个引用计算的功能,实现了ReferenceCounted引用计算接口中的方法
jvm使用可达性来进行垃圾回收,netty使用计数法来追踪ByteBuf的生命周期,,一个是pooled ByteBuf的支持,第二个是尽快的发现 那些可以回收的ByteBuf。
( Pooled ByteBuf:在传统的Buffer中,会被频繁的创建,使用,释放,这个是相当消耗时间的,从Netty4开始,新增了池化技术,把没有被引用的Buffer对象,放入对象缓冲池中。kafka中也有)
那里有引用,计算就+1,释放了就-1,如果为0,则需要释放ByteBuf,如果是堆内的,就使用GC来回收,如果是堆外的,就需要手动释放。
重要字段:
// long 类型
private static final long REFCNT_FIELD_OFFSET =
ReferenceCountUpdater.getUnsafeOffset(AbstractReferenceCountedByteBuf.class, "refCnt");
// 原子 字段updater
private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> AIF_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
//引用更新器 匿名类,抽象类的实现。 实现了两个方法,他们的返回值,就是上面的这两个字段。
private static final ReferenceCountUpdater<AbstractReferenceCountedByteBuf> updater =
new ReferenceCountUpdater<AbstractReferenceCountedByteBuf>() {
@Override
protected AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> updater() {
return AIF_UPDATER;
}
@Override
protected long unsafeOffset() {
return REFCNT_FIELD_OFFSET;
}
};
// 引用计数值,默认为2,为奇数表示已经释放了, 所以下面注解表示:并不是真实的引用计数。
// Value might not equal "real" reference count, all access should be via the updater
@SuppressWarnings("unused")
private volatile int refCnt = updater.initialValue();
存放在类字段中的引用计数,他们增加的算法,并不是引用一次就+1,所以不代表真实的引用次数,所以这里的真实的用于计算,是指实际上的引用次数,但是代码中被没有字段去保存。
AbstractReferenceCountedByteBuf类的部分方法:
public ByteBuf retain() { //真实计数+1
return updater.retain(this);
}
//真实计算+increment
public ByteBuf retain(int increment) {
return updater.retain(this, increment);
}
//获取当前对象
public ByteBuf touch() {
return this;
}
//外部可以调用的尝试释放资源
public boolean release() {
return handleRelease(updater.release(this));
}
3.1 retain
它的具体实现,是在ReferenceCountUpdater引用计数更新器里面的,方法名末尾加了一个0表示具体的实现。它同样是实现了ReferenceCounted接口。
//真实计数+1,引用计数+2
public final T retain(T instance) {
return retain0(instance, 1, 2);
}
// rawIncrement == increment << 1
private T retain0(T instance, final int increment, final int rawIncrement) {
int oldRef = updater().getAndAdd(instance, rawIncrement);
if (oldRef != 2 && oldRef != 4 && (oldRef & 1) != 0) { //如果老的是奇数
throw new IllegalReferenceCountException(0, increment);
}
// don't pass 0! 经过0,说明溢出了,要处理掉
if ((oldRef <= 0 && oldRef + rawIncrement >= 0)
|| (oldRef >= 0 && oldRef + rawIncrement < oldRef)) {
// overflow case 溢出
updater().getAndAdd(instance, -rawIncrement); //回滚
throw new IllegalReferenceCountException(realRefCnt(oldRef), increment);
}
return instance;
}
3.2 release
减少计数,会先获得引用计数,然后判断引用计数是否 是2,
//减少计数 1,返回 是否真正释放
public final boolean release(T instance) {
int rawCnt = nonVolatileRawCnt(instance); //获得引用计数
//如果为2,尝试释放。 不是 则自旋
return rawCnt == 2 ? tryFinalRelease0(instance, 2) || retryRelease0(instance, 1)
: nonFinalRelease0(instance, 1, rawCnt, toLiveRealRefCnt(rawCnt, 1));
}
//尝试最终释放,如果是2,则直接设置为1,释放内存,否者就失败。
private boolean tryFinalRelease0(T instance, int expectRawCnt) {
return updater().compareAndSet(instance, expectRawCnt, 1); // any odd number will work
}
//自旋设置 引用计数,或者尝试释放
private boolean retryRelease0(T instance, int decrement) {
for (;;) {
int rawCnt = updater().get(instance), realCnt = toLiveRealRefCnt(rawCnt, decrement);
if (decrement == realCnt) { //真实的计数和要减去的计数 是一样的话,尝试释放
if (tryFinalRelease0(instance, rawCnt)) {
return true;
}
} else if (decrement < realCnt) { //还不能释放,还有引用计数
// all changes to the raw count are 2x the "real" change
if (updater().compareAndSet(instance, rawCnt, rawCnt - (decrement << 1))) {
return false;
}
}...
}
最后:我们使用的很多缓存都是AbstractReferenceCountedByteBuf的子类。