Netty---ByteBuf认识

一,概念

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的子类。
 
 
 
                 https://www.jianshu.com/p/0f93834f23de (详细,有内容)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值