2023-09-13
原文作者:https://blog.csdn.net/wangwei19871103/category_9681495_2.html 原文地址: https://blog.csdn.net/wangwei19871103/article/details/104208786

ByteBuf简单介绍

其实是一个接口,但是有很多方法,可以理解成一个缓冲区数组,有一些操作的索引,比如读索引readerIndex表示从这个索引开始读,写索引writerIndex表示从这个索引开始写,还有缓冲区容量capacity等等。先看下类结构:

202309132159573081.png
有一个引用计数的接口,因为缓冲区用完了需要释放,这里用引用计数来管理。
其实缓冲区大致是这么个样子:

202309132159577162.png

一些特定的操作

discardReadBytes

把已读的区域数据给丢弃掉,其实就是把这部分区域给回收了,把读写索引都往前移,可写区域就大了:

202309132159598383.png

clear

这个并不是清除数据,只是重置读写索引到0,可写区域又变大了:

202309132200005864.png
其实方法有很多,一些不太好用图表示,后面会慢慢讲解的。

AbstractByteBuf

这个抽象字节缓冲区对ByteBuf的接口方法做了一些基本的实现。

202309132200014305.png
具体的实现都比较好理解,就不多说了,但是有一个要注意,就是他里面有个泄露检测器,应该是用来帮助检测内存泄露的,后面会说:

202309132200023186.png

ReferenceCounted引用计数接口

这个就是为了更好的管理好内存用的一种比较简单的方法,简单的原理就是如果一个对象有引用,那就计数器+1,如果释放了引用,那就计数器-1,如果发现计数器是0了,那就执行回收。一般的堆内的内存可以由GC来回收,但是如果是堆外的话,就要自己手动来释放啦,不然会造成内存泄露的。touch方法就是辅助调试用的,另外就是引用计数增加retain和减少release

202309132200029957.png

AbstractReferenceCountedByteBuf

这个字节缓冲区没干别的什么主要就是实现了引用计数器接口:

202309132200039058.png
我们来看看源码,其实都是调用了ReferenceCountUpdater的方法。

    public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {
        private static final long REFCNT_FIELD_OFFSET =//refCnt属性的内存偏移地址
                ReferenceCountUpdater.getUnsafeOffset(AbstractReferenceCountedByteBuf.class, "refCnt");
        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;
            }
        };
    
        // Value might not equal "real" reference count, all access should be via the updater 初始值
        @SuppressWarnings("unused")
        private volatile int refCnt = updater.initialValue();
    
        protected AbstractReferenceCountedByteBuf(int maxCapacity) {
            super(maxCapacity);
        }
    
        @Override
        boolean isAccessible() {
            // Try to do non-volatile read for performance as the ensureAccessible() is racy anyway and only provide
            // a best-effort guard.
            return updater.isLiveNonVolatile(this);//是否还能用,释放了就不能用了
        }
    
        @Override
        public int refCnt() {
            return updater.refCnt(this);//获取真实引用计数
        }
    
        /** 直接设置真实引用计数
         * An unsafe operation intended for use by a subclass that sets the reference count of the buffer directly
         */
        protected final void setRefCnt(int refCnt) {
            updater.setRefCnt(this, refCnt);
        }
    
        /** 重新设置真实计数
         * An unsafe operation intended for use by a subclass that resets the reference count of the buffer to 1
         */
        protected final void resetRefCnt() {
            updater.resetRefCnt(this);
        }
    //真实计数+1
        @Override
        public ByteBuf retain() {
            return updater.retain(this);
        }
    //真实计数+increment
        @Override
        public ByteBuf retain(int increment) {
            return updater.retain(this, increment);
        }
    //获取当前对象
        @Override
        public ByteBuf touch() {
            return this;
        }
    
        @Override
        public ByteBuf touch(Object hint) {
            return this;
        }
    //外部可以调用的尝试释放资源,内部是用引用更新器来判断的
        @Override
        public boolean release() {
            return handleRelease(updater.release(this));
        }
    
        @Override
        public boolean release(int decrement) {
            return handleRelease(updater.release(this, decrement));
        }
    //真正返回才去释放
        private boolean handleRelease(boolean result) {
            if (result) {
                deallocate();
            }
            return result;
        }
    
        /** 一旦真实计数为0就释放资源
         * Called once {@link #refCnt()} is equals 0.
         */
        protected abstract void deallocate();
    }

ReferenceCountUpdater

具体引用计数怎么实现的,主要是这个类。但是他并不是用普通的那种引用一次计数器加1,释放一次减1,而是用了奇数和偶数,如果还存在引用那么引用数是偶数,否则是奇数。同时引用一次会加2,释放一次也减2,获取真实的计数是引用计数无符号右移1位,看起来好像很奇怪,不过基本都是位操作和直接比较操作性能应该会提高点。比如我们初始的时候真实引用计数=1,但是内部引用计数=2。如果有一次释放就内部引用计数-2,两次就内部引用计数-4,当然引用的时候也一样,你会发现 只要有引用,内部引用计数值就是偶数 。我们举个例子,我引用了3次,内部引用计数=6,获取真实引用计数刚好6>>>1=3,如果释放了3次,前2次会将内部引用计数=2,但是最后一次如果发现内部引用计数=2的话,就会设置成1这样内部引用计数刚好是奇数,真实引用计数刚好是1>>>1=0,就可以释放内存了 。说了那么多,还是看源码吧,里面有体现。

realRefCnt

这个就是获取真实的计数,是引用计数>>>1,同时前面会判断引用计数是否是偶数,偶数才有引用,奇数就直接返回0了,这里开始并不是直接用&判断奇偶,而是直接用是否等于,这个比位操作更加快,可见netty在这提高性能方面真的做到了细节中的细节了,因为大部分真实的计数可能就是1或者2,所以前面两个只要直接判断相等即可:

    //获得真实计数 引用计数是奇数就返回0,说明已经释放了 偶数就无符号右移1 返回
        private static int realRefCnt(int rawCnt) {
            return rawCnt != 2 && rawCnt != 4 && (rawCnt & 1) != 0 ? 0 : rawCnt >>> 1;
        }

toLiveRealRefCnt

这个主要是在释放的时候内部用的,如果真实计数已经是0了,再释放就会报错,避免重复释放。

     /** 获取真实计数,如果真实引用已经是0了,就抛异常
         * Like {@link #realRefCnt(int)} but throws if refCnt == 0
         */
        private static int toLiveRealRefCnt(int rawCnt, int decrement) {
            if (rawCnt == 2 || rawCnt == 4 || (rawCnt & 1) == 0) {
                return rawCnt >>> 1;//偶数就无符号右移1
            }
            // odd rawCnt => already deallocated 奇数已经释放的
            throw new IllegalReferenceCountException(0, -decrement);
        }

refCnt

获取真实计数,但是不会抛异常。

    //获取真实计数
        public final int refCnt(T instance) {
            return realRefCnt(updater().get(instance));
        }

nonVolatileRawCnt

这个可以获取内部的引用计数,不是真实的。

    //可以根据偏移量获得引用计数,不是真实的计数
        private int nonVolatileRawCnt(T instance) {
            // TODO: Once we compile against later versions of Java we can replace the Unsafe usage here by varhandles.
            final long offset = unsafeOffset();
            return offset != -1 ? PlatformDependent.getInt(instance, offset) : updater().get(instance);
        }

isLiveNonVolatile

这个就是判断是否还存在引用,即内部的引用是否是偶数,是的话表示还有引用计数,返回true,不是就表示释放了,返回false,最后也是先判断是否相等来优化。

    //是否是还有真实计数
        public final boolean isLiveNonVolatile(T instance) {
            final long offset = unsafeOffset();
            final int rawCnt = offset != -1 ? PlatformDependent.getInt(instance, offset) : updater().get(instance);
    
            // The "real" ref count is > 0 if the rawCnt is even.偶数的话真实计数>0
            return rawCnt == 2 || rawCnt == 4 || rawCnt == 6 || rawCnt == 8 || (rawCnt & 1) == 0;
        }

setRefCnt

直接设置真实引用计数,可以看到如果正数就会乘以2,负数就直接是1,也就是说没设置成功。但是这里要注意refCnt << 1可能会是负数,溢出了,比如1173741824<<1 =-1947483648

      public final void setRefCnt(T instance, int refCnt) {
            updater().set(instance, refCnt > 0 ? refCnt << 1 : 1); // overflow OK here
        }

retain

这几个方法都是增加引用的,里面会判断溢出的问题。

    //真实计数+1,即引用计数+2
        public final T retain(T instance) {
            return retain0(instance, 1, 2);
        }
    //increment为正的才可以,但是rawIncrement 可能是负的,溢出了,后面会处理
        public final T retain(T instance, int increment) {
            // all changes to the raw count are 2x the "real" change - overflow is OK
            int rawIncrement = checkPositive(increment, "increment") << 1;
            return retain0(instance, increment, rawIncrement);
        }
    
        // rawIncrement == increment << 1 增量=真实增量x2
        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)//比如setRefCnt的时候设置了负数进去,oldRef =-1173741824,increment=1003741824 rawIncrement=2007483648
                    || (oldRef >= 0 && oldRef + rawIncrement < oldRef)) {//比如setRefCnt的时候设置了正数进去,oldRef =2,increment=1103741824 rawIncrement=-2087483648
                // overflow case 溢出了
                updater().getAndAdd(instance, -rawIncrement);//改回来
                throw new IllegalReferenceCountException(realRefCnt(oldRef), increment);
            }
            return instance;
        }

release

释放的时候会先调用nonVolatileRawCnt获得引用计数,然后判断引用计数是否是2或者减的值就是真实引用计数值,是的话就可以尝试直接设置的方法tryFinalRelease0,如果失败会去尝试释放方法retryRelease0,这个是自旋,直到成功为止。如果不是的话就普通的引用计数器值的修改即可nonFinalRelease0。当然这里可能会对引用已经是1的再进行释放,这样就会在retryRelease0中的toLiveRealRefCnt检测报异常,避免了重复释放,而且里面修改值都是原子操作,线程安全的。

    //减少计数1,返回是否真正释放
        public final boolean release(T instance) {
            int rawCnt = nonVolatileRawCnt(instance);//获取引用计数 如果引用计数rawCnt == 2 说明真实计数是1,就可以直接尝试最终释放,否则就真实计数减1,这个就算已经释放也不会报错
            return rawCnt == 2 ? tryFinalRelease0(instance, 2) || retryRelease0(instance, 1)
                    : nonFinalRelease0(instance, 1, rawCnt, toLiveRealRefCnt(rawCnt, 1));
        }
    //减少计数decrement,返回是否真正释放
        public final boolean release(T instance, int decrement) {
            int rawCnt = nonVolatileRawCnt(instance);//获取引用计数
            int realCnt = toLiveRealRefCnt(rawCnt, checkPositive(decrement, "decrement"));//获取真实计数
            return decrement == realCnt ? tryFinalRelease0(instance, rawCnt) || retryRelease0(instance, decrement)
                    : nonFinalRelease0(instance, decrement, rawCnt, realCnt);//
        }
    //尝试最终释放 如果引用计数是2的话,就直接设为1,释放内存,否则就失败
        private boolean tryFinalRelease0(T instance, int expectRawCnt) {
            return updater().compareAndSet(instance, expectRawCnt, 1); // any odd number will work
        }
    
        private boolean nonFinalRelease0(T instance, int decrement, int rawCnt, int realCnt) {
            if (decrement < realCnt //更新引用计数
                    // all changes to the raw count are 2x the "real" change - overflow is OK
                    && updater().compareAndSet(instance, rawCnt, rawCnt - (decrement << 1))) {
                return false;
            }
            return retryRelease0(instance, decrement);
        }
    //自旋设置引用计数或者尝试释放
        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) {//真实计数大于减去的计数,还不能释放,只是减去decrement
                    // all changes to the raw count are 2x the "real" change
                    if (updater().compareAndSet(instance, rawCnt, rawCnt - (decrement << 1))) {
                        return false;
                    }
                } else {
                    throw new IllegalReferenceCountException(realCnt, -decrement);
                }
                Thread.yield(); // this benefits throughput under high contention 提示释放CPU,增加吞吐量
            }
        }

总结

今天主要先讲下这个引用计数缓冲区的实现,后面很多我们常用的缓冲区都是这个的子类:

202309132200048119.png
其他的后面再讲吧,现在我们知道了很多缓冲区是实现引用计数接口的就行了,主要还是要释放内存,特别是堆外的内存。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

阅读全文