ByteBuf reference count of Netty source code analysis

Reference counting is a common memory management mechanism. It refers to the process of saving the number of references of resources and releasing them when the number of references becomes zero. Netty at 4 Version x began to use the reference counting mechanism to manage some objects. Its implementation idea is not particularly complex. It mainly involves tracking the number of times an object is referenced. In the specific code of netty, the object requiring memory management through reference count will be implemented based on the ReferenceCounted interface. When the reference count is greater than 0, it means that the object will not be released by reference. When the reference count is reduced to 0, the object will be released. Through the reference counting mechanism, netty can well realize memory management. When the reference count is reduced to 0, either directly release the memory or put it back into the memory pool for reuse.

1. Basic example

Let's take a look at the use of the reference counting mechanism in Netty through a simple example

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        
           ByteBuf recvBuffer = (ByteBuf) msg;// Applying for ByteBuf requires active release
        if(recvBuffer.isDirect()){
            System.err.println(true);
        }
        PooledByteBufAllocator allocator = new PooledByteBufAllocator(true);
        ByteBuf sendBuffer = allocator.buffer();//Request pooled direct memory
        System.err.println("sendBuffer Reference count for:"+sendBuffer.refCnt());
        sendBuffer.retain();
        System.err.println("sendBuffer Reference count for:"+sendBuffer.refCnt());
        sendBuffer.release();
        System.err.println("sendBuffer Reference count for:"+sendBuffer.refCnt());

        try {
            byte[] bytesReady = new byte[recvBuffer.readableBytes()];
            recvBuffer.readBytes(bytesReady);
            System.out.println("channelRead Data received:"+ BytesUtils.toHexString(bytesReady));
            byte[] sendBytes = new byte[] {0x7E,0x01,0x02,0x7e};
            sendBuffer.writeBytes(sendBytes);
            ctx.writeAndFlush(sendBuffer);
            System.err.println("sendBuffer Reference count for:"+sendBuffer.refCnt());
        }catch (Exception e) {
            // TODO: handle exception
            System.err.println(e.getMessage());
        }finally {
            System.err.println("recvBuffer Reference count for:"+recvBuffer.refCnt());
            recvBuffer.release(); //Release required here
            System.err.println("recvBuffer Reference count for:"+recvBuffer.refCnt());
        }
 
    }

The output results are as follows. It can be seen from the example that the retain method will increase the count reference and the release method will reduce the count reference

true
sendBuffer Reference count for:1
sendBuffer Reference count for:2
sendBuffer Reference count for:1
sendBuffer Reference count for:0
recvBuffer Reference count for:1
recvBuffer Reference count for:0

AbstractReferenceCountedByteBuf implements memory management of ByteBuf to recover, release or reuse memory. The inheritance and implementation relationship of AbstractReferenceCountedByteBuf is shown in the following figure

2. ReferenceCounted interface definition

 

The first is the definition of the ReferenceCounted interface

public interface ReferenceCounted {
    /**
     * Returns the reference count of this object.  If {@code 0}, it means this object has been deallocated.
     * Returns the reference count of the object
     */
    int refCnt();

    /**
     * Increases the reference count by {@code 1}.
     * Increase reference count
     */
    ReferenceCounted retain();

    /**
     * Increases the reference count by the specified {@code increment}.
     * The reference count is incremented by the specified value
     */
    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)}.
     * Record the current access location of the object for debugging.
     * If it is determined that the object has been compromised, the information recorded by this operation will be provided to you
     */
    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}.
     * Record the current access location of the object, and additional information is used for debugging.
     * If it is determined that the object has been compromised, the information recorded by this operation will be provided to you
     */
    ReferenceCounted touch(Object hint);

    /**
     * Decreases the reference count by {@code 1} and deallocates this object if the reference count reaches at
     * {@code 0}.
     *
     * @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated
     * The reference count decreases. If the count becomes 0, the object resource is released
     * Returns true if the object resource is released, otherwise false
     */
    boolean release();

    /**
     * Decreases the reference count by the specified {@code decrement} and deallocates this object if the reference
     * count reaches at {@code 0}.
     *
     * @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated
     * Reference count - specifies the value. If the count becomes 0, the object resource is released or handed back to the object pool
     * Returns true if the object resource is released, otherwise false
     */
    boolean release(int decrement);
}

3. AbstractReferenceCountedByteBuf source code analysis

AbstractReferenceCountedByteBuf implements ReferenceCounted specifically. The retain and release methods operate the reference count refcnt through CAS. The following is a brief analysis of its source code

initialization

The reference count initial value refCnt is modified with the keyword volatile to ensure the visibility of the thread. At the same time, even numbers are used. The reference increase is realized through displacement operation to improve the operation efficiency.

The AtomicIntegerFieldUpdater object is used to update refCnt through CAS to achieve thread safety, avoid locking and improve efficiency.

    private static final long REFCNT_FIELD_OFFSET;
    //Use AtomicIntegerFieldUpdater object to update refCnt in CAS mode
    private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater =
            AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");

    //The actual value of refCnt is even, and the displacement operation is adopted to improve the efficiency
    // even => "real" refcount is (refCnt >>> 1); odd => "real" refcount is 0
    @SuppressWarnings("unused")
    private volatile int refCnt = 2;

retain operation

In the above example, each time the retain method is called, the reference count will be accumulated once. Let's take a look at the specific implementation of retain in the source code

    public ByteBuf retain() {
        return retain0(1);
    }

    @Override
    public ByteBuf retain(int increment) {
        return retain0(checkPositive(increment, "increment"));
    }

    //Counter increment operation
    private ByteBuf retain0(final int increment) {
        // all changes to the raw count are 2x the "real" change
        int adjustedIncrement = increment << 1; // overflow OK here the real count is increased by 2 times
        int oldRef = refCntUpdater.getAndAdd(this, adjustedIncrement); //Increment and obtain the original value through CAS
        if ((oldRef & 1) != 0) {//Judge parity. Normally, it should be even here
            throw new IllegalReferenceCountException(0, increment);
        }
        // don't pass 0!   If the count is less than or equal to 0 and the integer range is out of bounds (0x7fffff + 1), an exception is thrown
        if ((oldRef <= 0 && oldRef + adjustedIncrement >= 0)
                || (oldRef >= 0 && oldRef + adjustedIncrement < oldRef)) {
            // overflow case
            refCntUpdater.getAndAdd(this, -adjustedIncrement);
            throw new IllegalReferenceCountException(realRefCnt(oldRef), increment);
        }
        return this;
    }

release operation

Call the release method to devalue the reference count. For the specific implementation of release in the source code, note that since the reference count increases by 2 times, the number of references = reference count / 2. When increment = refcnt / 2, that is, the number of references = release times, it means that ByteBuf is no longer referenced and performs the operation of releasing memory or putting it back into the memory pool.

    //Counter devaluation operation
    private boolean release0(int decrement) {
        int rawCnt = nonVolatileRawCnt(), realCnt = toLiveRealCnt(rawCnt, decrement); //Divide the counter by 2, that is, the number of references
        /**
         * /Note that when you pass in the devaluation parameter increment = realcnt, it is equivalent to the number of references = the number of releases. Release directly
         */
        if (decrement == realCnt) {
            if (refCntUpdater.compareAndSet(this, rawCnt, 1)) { //CAS mode is set to 1
                deallocate();//Free or put memory back into memory pool
                return true;
            }
            return retryRelease0(decrement);//Enter specific operation
        }
        return releaseNonFinal0(decrement, rawCnt, realCnt);
    }

    private boolean releaseNonFinal0(int decrement, int rawCnt, int realCnt) {
        //If the increment is less than realCnt, subtract increment * 2 through CAS
        if (decrement < realCnt
                // all changes to the raw count are 2x the "real" change
                && refCntUpdater.compareAndSet(this, rawCnt, rawCnt - (decrement << 1))) {
            return false;
        }
        return retryRelease0(decrement);
    }

    private boolean retryRelease0(int decrement) {
        for (;;) {
            int rawCnt = refCntUpdater.get(this), realCnt = toLiveRealCnt(rawCnt, decrement);
            if (decrement == realCnt) {
                if (refCntUpdater.compareAndSet(this, rawCnt, 1)) {
                    deallocate();
                    return true;
                }
            } else if (decrement < realCnt) {//If the increment is less than realCnt, subtract increment * 2 through CAS
                // all changes to the raw count are 2x the "real" change
                if (refCntUpdater.compareAndSet(this, rawCnt, rawCnt - (decrement << 1))) {
                    return false;
                }
            } else {
                throw new IllegalReferenceCountException(realCnt, -decrement);
            }
            Thread.yield(); // this benefits throughput under high contention
        }
    }

    /**
     * Like {@link #realRefCnt(int)} but throws if refCnt == 0
     */
    private static int toLiveRealCnt(int rawCnt, int decrement) {
        if ((rawCnt & 1) == 0) {
            return rawCnt >>> 1;
        }
        // odd rawCnt => already deallocated
        throw new IllegalReferenceCountException(0, -decrement);
    }

4. Summary

Above, we analyzed the specific implementation of Netty reference counting around AbstractReferenceCountedByteBuf. We can see that Netty not only implements reference counting, but also combines CAS, displacement calculation and other methods to ensure operation efficiency and thread safety. We can also refer to similar application scenarios in actual projects, such as data transmission times, Implementation of counting scenarios such as remaining quantity of goods. I hope this article can be helpful to you. If there are deficiencies and inaccuracies, please correct and forgive me. Thank you very much.

 

Keywords: Java Netty Interview

Added by juancarlosc on Tue, 04 Jan 2022 14:49:35 +0200