吃透Netty源码系列十五之ByteBuf和引用计数_王伟王胖胖的博客-程序员秘密

技术标签: 多路复用  吃透Netty源码系列  Netty源码解析  nio  Netty  

ByteBuf简单介绍

其实是一个接口,但是有很多方法,可以理解成一个缓冲区数组,有一些操作的索引,比如读索引readerIndex表示从这个索引开始读,写索引writerIndex表示从这个索引开始写,还有缓冲区容量capacity等等。先看下类结构:
在这里插入图片描述
有一个引用计数的接口,因为缓冲区用完了需要释放,这里用引用计数来管理。
其实缓冲区大致是这么个样子:
在这里插入图片描述

一些特定的操作

discardReadBytes

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

clear

这个并不是清除数据,只是重置读写索引到0,可写区域又变大了:
在这里插入图片描述
其实方法有很多,一些不太好用图表示,后面会慢慢讲解的。

AbstractByteBuf

这个抽象字节缓冲区对ByteBuf的接口方法做了一些基本的实现。
在这里插入图片描述
具体的实现都比较好理解,就不多说了,但是有一个要注意,就是他里面有个泄露检测器,应该是用来帮助检测内存泄露的,后面会说:
在这里插入图片描述

ReferenceCounted引用计数接口

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

AbstractReferenceCountedByteBuf

这个字节缓冲区没干别的什么主要就是实现了引用计数器接口:
在这里插入图片描述
我们来看看源码,其实都是调用了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,增加吞吐量
        }
    }

总结

今天主要先讲下这个引用计数缓冲区的实现,后面很多我们常用的缓冲区都是这个的子类:
在这里插入图片描述
其他的后面再讲吧,现在我们知道了很多缓冲区是实现引用计数接口的就行了,主要还是要释放内存,特别是堆外的内存。

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wangwei19871103/article/details/104208786

智能推荐

趣学python3(25)-del,deepcopy以及内存引用计数_水木森的博客-程序员秘密

#code:刘兴num1=12num2=13num3=num1+num2num4=num1print(num3)del num2del num1print(num4)#12的引用计数由2变为1,因为num4占用了一个引用计数,num1引用计数已经减一。print(num3)#12+13的结果依然有一个引用计数num3x=[1,2,3]y=x[1]del x[1]print...

一个完整的性能测试流程_weixin_33691817的博客-程序员秘密

下午逛一个测试交流群时,聊起性能测试,然后某位群成员说他们用的loadrunner做性能,当时觉得这话有点偏颇,虽然我也是一个性能测试道路上的摸索前进者。。。诚然,我们在进行性能测试工作的过程中,需要借助工具的辅助来帮我们完成一些工作,但loadrunner≠性能测试!或者说,性能测试工具≠性能测试,工具永远是一种辅助的工具,而不能认为会用工具就会性能测试了!希望看到这里的童鞋(测试小白这...

常用的函数_gbsck的博客-程序员秘密

本文来自www.sql163.com==================================================================================================将传入的参数转换成整数, 并限制允许的上下限@param: number, 需要转换的参数Function ToInt(number, min, max

dubbo入门官方案例学习_往前的的博客-程序员秘密

概述:凡是先入门,而然后破门而出,不深究,所为何?难矣难矣,简单来说就是从入门到放弃。dubbo官网1、画一画dubbo架构粗略图这个框架,让我想起,好像类似QQ添加特别关心功能。只要特别关心的人有最新动态你都会第一时间通知到。所有信息首先会在一个地方报个到,然后进行转发通知特定对象。还是来看看dubbo过程(个人理解)0、启动服务,做好向外提供服务的准

随便推点

上千程序员在支付宝开party?——记 BASIC College 超级技术嘉年华_支付宝技术团队的博客-程序员秘密

5 月 31 日,一年一度的支付宝 BASIC College 超级技术嘉年华开幕。上千名程序员集结在一起,探讨金融科技自主研发、融合创新的历程与最新实践。严苛的技术期中大考、完整的支付宝小程序技术栈、全系列数据智能产品、金融级分布式中间件、区块链爱情树等支付宝金融科技也都亮相于这场技术大趴体!BASIC College技术嘉年华现场的“People mountain people sea”...

win7及以上系统安装MongoDB_win7支持最高mongo版本_风致﹏的博客-程序员秘密

win7及以上系统安装MongoDB下载地址:https://www.mongodb.com/download-center对版本的解释:MongoDB for Windows 64-bit 适合 64 位的 Windows Server 2008 R2+, Windows 7 , 及最新版本的 Window 系统。MongoDB for Windows 32-bit 适合 32 位的 ...

【调剂】中科院上海微系统与信息技术研究所2023年高校联培项目招收调剂生的通知..._计算机与软件考研的博客-程序员秘密

公众号【计算机与软件考研】每天都会发布最新的计算机考研调剂信息!点击公众号界面左下角的调剂信息或者公众号回复“调剂”是计算机/软件等专业的所有调剂信息集合,会一直更新的。各位考生: 2023年,中科院上海微系统所拟开通与上海科技大学高校联合培养的调剂选拔,目前尚有极少余额,凡有意愿并满足以下条件的考生可按要求报名。 1、物质学院:总分及单科均符合工科国家线的要求(英语一或...

WEB前端(HTML、XML、CSS、JS)学习笔记_me4405801的博客-程序员秘密

HTMLHTML: HyperText Markup Language 超文本标记语言。HTML是最基础的网页语言。HTML的代码都是由标签所组成。HTML的基本格式 存放属性的信息,辅助性的信息 引入外部的文件(重要) 会先加载 存放的是真正的数据 多数标签都是有开

Promise.all 循环中调用接口_promise循环请求接口_怪兽的猫的博客-程序员秘密

开源商城export default { methods: { getImageBase64(images) { // 生成一个数组 let spreadList = [] images.forEach(item =&gt; { const oneApi = imageBase64({ url: item.pic }).then(res =&gt; { return res.data.code;

python + openCV图像处理(五)_python opencv图像处理_我才是阿鑫的博客-程序员秘密

本篇博客将介绍 形态学操作、图像梯度 两大部分。

推荐文章

热门文章

相关标签