您当前的位置: 首页 > 

恐龙弟旺仔

暂无认证

  • 0浏览

    0关注

    282博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Netty源码解析-ByteBuf的引用计数

恐龙弟旺仔 发布时间:2021-12-23 19:08:12 ,浏览量:0

前言:

    之前介绍ByteBuf基本API及相关实现类时,不知道大家有没有注意到,ByteBuf抽象类实现了ReferenceCounted接口。这个接口是做什么的呢?主要是做计数用的。为什么需要计数呢?我们暂且不表,先分析下这个接口的若干方法如何实现计数功能,最后再说明下计数使用的场景。

1.ReferenceCounted接口的基本方法
public interface ReferenceCounted {
 
    /**
     * Returns the reference count of this object.  If {@code 0}, it means this object has been deallocated.
     * 获取对象的引用数
     */
    int refCnt();

    /**
     * Increases the reference count by {@code 1}.
     * 将对象引用数+1
     */
    ReferenceCounted retain();

    /**
     * Increases the reference count by the specified {@code increment}.
     * 将对象引用数+increment个
     */
    ReferenceCounted retain(int increment);

    /**
     * Records the current access location of this object for debugging purposes.
     * If this object is determined to be leaked, the information recorded by this operation will be provided to you
     * via {@link ResourceLeakDetector}.  This method is a shortcut to {@link #touch(Object) touch(null)}.
     */
    ReferenceCounted touch();

    /**
     * Records the current access location of this object with an additional arbitrary information for debugging
     * purposes.  If this object is determined to be leaked, the information recorded by this operation will be
     * provided to you via {@link ResourceLeakDetector}.
     */
    ReferenceCounted touch(Object hint);

    /**
     * Decreases the reference count by {@code 1} and deallocates this object if the reference count reaches at
     * {@code 0}.
     *
     * 将对象引用数-1
     */
    boolean release();

    /**
     * Decreases the reference count by the specified {@code decrement} and deallocates this object if the reference
     * count reaches at {@code 0}.
     * 将对象引用数-decrement个,当对象引用数为0时,代表当前对象可被释放
     * @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated
     */
    boolean release(int decrement);
}

英文注释还是比较给力的,通过注释我们可以了解到每个方法的含义。就是这个touch()方法不太好理解,没关系,后续看看能不能通过代码来理解到。

2.寻找ReferenceCounted接口实现类

    一头雾水的时候,我们先去寻找接口的实现类,在ByteBuf中,我们没有找到,那么继续寻找其子类实现,终于在AbstractReferenceCountedByteBuf中我们找到了。

具体看下其实现

public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {
    
 	private static final long REFCNT_FIELD_OFFSET =
            ReferenceCountUpdater.getUnsafeOffset(AbstractReferenceCountedByteBuf.class, "refCnt");
    private static final AtomicIntegerFieldUpdater AIF_UPDATER =
            AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");

    // ReferenceCountUpdater作为更新器,计数器的主要功能都交由该类来实现
   private static final ReferenceCountUpdater updater =
            new ReferenceCountUpdater() {
        protected AtomicIntegerFieldUpdater updater() {
            return AIF_UPDATER;
        }
        @Override
        protected long unsafeOffset() {
            return REFCNT_FIELD_OFFSET;
        }
    };
    
    // 计数信息
    private volatile int refCnt = updater.initialValue();
    
    // 获取计数器
    public int refCnt() {
        return updater.refCnt(this);
    }
    
    // 增加计数
    public ByteBuf retain() {
        return updater.retain(this);
    }
    
    // 减少计数
    public boolean release() {
        return handleRelease(updater.release(this));
    }
    
	private boolean handleRelease(boolean result) {
        if (result) {
            deallocate();
        }
        return result;
    }
}

通过之前ByteBuf基本API的分析中,我们知道,AbstractReferenceCountedByteBuf作为一个公共子类,基本所有重要的ByteBuf实现类都继承了该子类。那么可以理解为所有的ByteBuf都有计数器功能。

既然功能都交由ReferenceCountUpdater来实现了,那么我们来分析下该类。

3.ReferenceCountUpdater
public abstract class ReferenceCountUpdater {
    /*
     * Implementation notes:
     *
     * For the updated int field:
     *   Even => "real" refcount is (refCnt >>> 1)
     *   Odd  => "real" refcount is 0
     *
     * (x & y) appears to be surprisingly expensive relative to (x == y). Thus this class uses
     * a fast-path in some places for most common low values when checking for live (even) refcounts,
     * for example: if (rawCnt == 2 || rawCnt == 4 || (rawCnt & 1) == 0) { ...
     */

    protected ReferenceCountUpdater() { }
    protected abstract AtomicIntegerFieldUpdater updater();
    public final int refCnt(T instance) {
        return realRefCnt(updater().get(instance));
    }
    ...
}

还是一脸懵逼,除了留下一堆奇怪的注释,就是把功能交由AtomicIntegerFieldUpdater来实现了。

回过头来看,这里的AtomicIntegerFieldUpdater 就是 AbstractReferenceCountedByteBuf.AIF_UPDATER属性。没办法,回过头先学习AIF_UPDATER吧。

4.AIF_UPDATER
private static final AtomicIntegerFieldUpdater AIF_UPDATER =
            AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");

第一次见到AtomicIntegerFieldUpdater,AtomicInteger笔者倒是用过,用来进行Integer的原子操作。那AtomicIntegerFieldUpdater呢?具体的笔者不再赘述,大家可以参考下 笑谈java并发编程四之AtomicIntegerFieldUpdater介绍_我要上天-CSDN博客  这篇文章,里面有其使用示例。

总体来说,就是通过AIF_UPDATER,可以实现对AbstractReferenceCountedByteBuf.refCnt属性(也就是对象引用计数器)的原子加减操作。

Q:那么问题来呢?为什么不直接将refCnt属性设置为AtomicInteger呢?

A:因为ByteBuf在Netty中使用的很多,如果这么多的对象在计数时都使用AtomicInteger而不是int基本类型,那么则是一笔很大的开销。所以为了Netty为了性能,使用全局的AIF_UPDATER来对refCnt这个基本属性进行原子操作。

好了,分析完AIF_UPDATER之后,我们可以回到正题了,继续对ReferenceCountUpdater的基本方法进行分析

5.ReferenceCountUpdater的API
public abstract class ReferenceCountUpdater {
 
    /*
     * Implementation notes:
     *
     * For the updated int field:
     *   Even => "real" refcount is (refCnt >>> 1)
     *   Odd  => "real" refcount is 0
     *
     * (x & y) appears to be surprisingly expensive relative to (x == y). Thus this class uses
     * a fast-path in some places for most common low values when checking for live (even) refcounts,
     * for example: if (rawCnt == 2 || rawCnt == 4 || (rawCnt & 1) == 0) { ...
     */
    
    public final int initialValue() {
        return 2;
    }

    // 获取对象真实引用值
    private static int realRefCnt(int rawCnt) {
        // 若rawCnt为奇数,则直接返回0
        return rawCnt != 2 && rawCnt != 4 && (rawCnt & 1) != 0 ? 0 : rawCnt >>> 1;
    }
    
    // 增加计数
    public final T retain(T instance) {
        return retain0(instance, 1, 2);
    }

    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") = 0 && oldRef + rawIncrement < oldRef)) {
            // overflow case
            updater().getAndAdd(instance, -rawIncrement);
            throw new IllegalReferenceCountException(realRefCnt(oldRef), increment);
        }
        return instance;
    }
    
    // 减少计数
    public final boolean release(T instance) {
        // 获取引用计数
        int rawCnt = nonVolatileRawCnt(instance);
        // 若是2,则说明只有一个引用,那么再减少一次的话,就可以彻底释放instance
        // 若非2,则说明还有多次引用,那么就正常减少一次
        return rawCnt == 2 ? tryFinalRelease0(instance, 2) || retryRelease0(instance, 1)
                : nonFinalRelease0(instance, 1, rawCnt, toLiveRealRefCnt(rawCnt, 1));
    }
    
    // 将引用值设置为1,奇数说明当前instance无引用,后续可被释放
    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) {
        // 直接将rawCnt-2
        if (decrement < realCnt
                // all changes to the raw count are 2x the "real" change - overflow is OK
                && updater().compareAndSet(instance, rawCnt, rawCnt - (decrement >> 1,比如若rawCnt=8,则说明真实引用值为8 >>> 1 = 4。

    所以,对ReferenceCountUpdater而言,每增加一次引用,则计数器+2,每减少一次引用,则计数器-2,若最终引用为2时,则设置为1,说明当前对象已经没有被引用了。

6.计数器被调用的那些场景

    上面讲述了那么多,那么计数器是在什么时候被调用的呢?什么时候对计数器进行加减操作的呢,貌似没有手动调用过。那么我们可以先通过一个示例手动调用下。

6.1 手动进行ByteBuf的引用与释放
ByteBuf buf = Unpooled.directBuffer(512);
System.out.println(buf);
try {
    // 业务操作,将数据存入buf

} finally {
    // 最终业务完成,将buf释放
    buf.release();
}

示例比较简单,我们获取一个DirectByteBuf(非池化的),在一系列的业务操作后,最终将使用完成的ByteBuf释放掉(调用release方法)。

释放方法在release中,我们来看下这个方法的操作

6.1.1 ByteBuf.release() 

    最终实现在其子类AbstractReferenceCountedByteBuf中

public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {
    public boolean release(int decrement) {
        // 使用上面分析的 updater 来进行当前对象的引用数减少
        // 若引用数比较多,则updater.release()返回false,因为还有其他引用;否则返回true,意味着所有的引用都已经被释放
        return handleRelease(updater.release(this, decrement));
    }

    private boolean handleRelease(boolean result) {
        // 若当前实例没有被引用了,则直接调用deallocate进行内存释放
        if (result) {
            // 在具体子类中实现,笔者不再赘述
            deallocate();
        }
        return result;
    }
}

上面的示例是我们手动调用的释放,那么在实际的应用中这个ByteBuf是如何释放的呢?

这个不是本文的重点,笔者可以提示下,就是在SimpleChannelInboundHandler.channelRead()方法中

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    boolean release = true;
    try {
        if (acceptInboundMessage(msg)) {
            @SuppressWarnings("unchecked")
            I imsg = (I) msg;
            channelRead0(ctx, imsg);
        } else {
            release = false;
            ctx.fireChannelRead(msg);
        }
    } finally {
        if (autoRelease && release) {
            // 在这里进行释放
            // 最终调用((ReferenceCounted) msg).release()减少计数
            ReferenceCountUtil.release(msg);
        }
    }
}
总结:

    本文详细介绍了ByteBuf的引用计数器的实现,也学到了一些之前没有接触过的知识点(比如AtomicIntegerFieldUpdater)。算是大体上对引用计数有一定的了解了。

    那么问题来了,费了这么大功夫了解引用计数,最终是做什么用的呢?暂且不表,下篇博客中来介绍。

关注
打赏
1655041699
查看更多评论
0.0363s