Skip to content

Latest commit

 

History

History
221 lines (166 loc) · 8.01 KB

22.2.ReferenceCounted.md

File metadata and controls

221 lines (166 loc) · 8.01 KB
title date categories tags description
22.2.ReferenceCounted
2020-04-09
Netty
Netty
网络
零拷贝
public class ServerSimpleHandler2 extends SimpleChannelInboundHandler<ByteBuf> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { // PooledUnsafeDirectByteBuf(freed)
       System.out.println(ReferenceCountUtil.refCnt(msg) + "   " + msg.refCnt());
        System.out.println(ReferenceCountUtil.refCnt(msg) + "   " + msg.refCnt());
//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//                try {
//                    Thread.sleep(2000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//
//                }
//                System.out.println(ReferenceCountUtil.refCnt(msg) + "   " + msg.refCnt());
//
//                //下面的两行代码都会报错:Exception in thread "Thread-1" io.netty.util.IllegalReferenceCountException: refCnt: 0
//                //就是因为 msg 已经被释放了。虽然断点看的时候, msg里面的一些属性还是有值的, 但是调用就报错。
//                //long l = msg.memoryAddress();
//                //byte b = msg.readByte();
//
//                //客户端不会收到, 并且报错:io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1,并且客户端与客户端的连接channel 会被自动关闭了
//                ctx.writeAndFlush(msg);
//            }
//        }).start();


        // 不管是采用注释里面写的方法, 延时 两秒发送, 主要为了等finally调用:ReferenceCountUtil.release(msg); 完成
        // 还是下面的直接调用 ctx.write(msg); 都会报错。
        // 因为write 是异步的, 调用的时候, msg底层的ByteBuf可能已经被释放掉了。
        
      
        // 调用下面这一行代码,客户端能收到, 但是报错:io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1, 与客户端连接的channel 没有被关闭
        ctx.writeAndFlush(msg);
    }
}

ReferenceCountUtil 的refCnt 方法, 内部也是使用msg.refCnt 来实现的

/**
 * Returns reference count of a {@link ReferenceCounted} object. If object is not type of
 * {@link ReferenceCounted}, {@code -1} is returned.
 */
public static int refCnt(Object msg) {
    return msg instanceof ReferenceCounted ? ((ReferenceCounted) msg).refCnt() : -1;
}
// Value might not equal "real" reference count, all access should be via the updater
@SuppressWarnings("unused")
private volatile int refCnt = updater.initialValue();

注意:ByteBuf有个refCnt()方法, 还有个 refCnt 属性。这个refCnt属性是不准确的。

ByteBuf 是因为 实现了 ReferenceCounted这个接口,才有 refCnt()方法

/**
 * A reference-counted object that requires explicit deallocation.
 ReferenceCounted 是需要显式释放的引用计数的对象。
 * <p>
 * When a new {@link ReferenceCounted} is instantiated, it starts with the reference count of {@code 1}.
 * {@link #retain()} increases the reference count, and {@link #release()} decreases the reference count.
 * If the reference count is decreased to {@code 0}, the object will be deallocated explicitly, and accessing
 * the deallocated object will usually result in an access violation.
 当一个 ReferenceCounted对象被实例化之后, 这个对象的引用计数会被初始化为1.
 retain方法用于增加引用计数,  relaease用于减少引用计数
 如果这个对象的引用计数减少到0, 那么这个对象就会被显示的回收了, 后续访问被回收的对象通常会导致访问冲突。就是出错了。
 
 我的理解:这个引用计数就是Netty自己实现的一套对象管理的工具。
 Netty自己帮我们维护一个引用计数。如果引用计数为0,就代表在Netty层面, 对象被释放了。不管Java的垃圾回收机制到底把内存回收了没有,反正在Netty层面是认为它已经不能再使用了。
 当我们使用这个对象的时候,Netty首先会帮我们检查 这个对象的引用计数是不是0,如果是,那么好了, 就认为在Netty层面该对象已经被释放,就直接抛出异常了IllegalReferenceCountException。
 这样就能解释了上面我们代码里面
 
  // System.out.println(ReferenceCountUtil.refCnt(msg) + "   " + msg.refCnt());  // 打印 0   0
  // 当msg的引用计数为0的时候,虽然看断点中 msg里面的一些属性还是有值的, 但是调用方法就报错。
  //                //long l = msg.memoryAddress();
  //                //byte b = msg.readByte();
 
 
 * </p>
 * <p>
 * If an object that implements {@link ReferenceCounted} is a container of other objects that implement
 * {@link ReferenceCounted}, the contained objects will also be released via {@link #release()} when the container's
 * reference count becomes 0.
 如果一个容器对象(比如List) 实现了ReferenceCounted,并且它所包含的元素也都实现了ReferenceCounted, 那么, 当这个容器的 引用计数变成0的时候, 容器所包含的所有对象都会被释放了。
 * </p>
 */
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}.
     */
    ReferenceCounted retain();

    /**
     * Increases the reference count by the specified {@code 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}.
     *
     * @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated
     */
    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
     */
    boolean release(int decrement);
}

上面 调用 memoryAddress 报错的原因:实际是调用 PooledUnsafeDirectByteBuf 的 memoryAddress。是返回了直接内存的内存地址,就是个long型的。

@Override
public long memoryAddress() {
    ensureAccessible();
    return memoryAddress;
}

ensureAccessible();

/**
 * Should be called by every method that tries to access the buffers content to check
 * if the buffer was released before.
 任何一个尝试访问缓冲区的方法, 都应该先调用这个方法, 去以检查缓冲区是否已释放
 */
protected final void ensureAccessible() {
    if (checkAccessible && !isAccessible()) {
        throw new IllegalReferenceCountException(0);
    }
}

isAccessible

/**
 * Used internally by {@link AbstractByteBuf#ensureAccessible()} to try to guard
 * against using the buffer after it was released (best-effort).
 */
boolean isAccessible() {
    return refCnt() != 0;
}

真相了, 这里判断了 refCnt() 这个方法的返回值是否是0, 如果是0, 就代表不允许访问了。

到这里, 我也大概明白了 ReferenceCounted