Netty之引用计数

Netty中实现引用计数的接口是ReferenceCounted,一个实现了ReferenceCounted接口的类就具备了引用计数的能力。
当一个引用计数的对象被初始化时, 引用计数器被初始化为1。
ReferenceCounted接口中两个主要的方法:
retain()方法对引用计数加1,release()方法对引用计数减1。如果引用计数被减到0,则这个对象就将被显式的回收,此时再来访问该对象则会抛出IllegalReferenceCountException异常。

当一个引用计数对象被初始化后,引用计数器的值为1,如图image1所示:

当调用retain()方法,引用计数器的值加1,如图image2所示:

当调用retain(2)方法,引用计数器的值加2,如图image3所示:

当调用release(2)方法,引用计数器的值减2,如图image4所示:

当调用release()方法,引用计数器的值减1,如图image5所示:

当调用release()方法,引用计数器的值减1,当引用计数器的值变为0时,该引用计数对象将被回收,如图image6所示:

使用不当常常会引起IllegalReferenceCountException异常,该异常的定义如下:

public IllegalReferenceCountException(int refCnt, int increment) {
    this("refCnt: " + refCnt + ", " + (increment > 0? "increment: " + increment : "decrement: " + -increment));
}

相信这段异常,接触过netty的一定不会陌生。
其中refCnt表示的是当前引用计数的值,increment表示要增加的计数值。

PS:当increment大于0,则表示是在增加引用计数时出现错误,否则是在减少引用计数时出现的错误。

该异常在retain()和release()方法中都有可能会抛出:

  • 在retain方法中
private ReferenceCounted retain0(int increment) {
    for (;;) {
        int refCnt = this.refCnt;
        final int nextCnt = refCnt + increment;
        // 当nextCnt<=increment时,表面refCnt==0,说明该对象已经被回收了
        // 若此时再来增加引用计数的值,则抛出非法引用的异常
        if (nextCnt <= increment) {
            throw new IllegalReferenceCountException(refCnt, increment);
        }
        if (refCntUpdater.compareAndSet(this, refCnt, nextCnt)) {
            break;
        }
    }
    return this;
}
  • 在release方法中
private boolean release0(int decrement) {
    for (;;) {
        int refCnt = this.refCnt;
        // 由于decrement最小为1,要满足refCnt<1成立,则refCnt==0,则说明该对象已经被回收了
        // 若此时再来减少引用计数的值,则抛出非法引用的异常
        if (refCnt < decrement) {
            throw new IllegalReferenceCountException(refCnt, -decrement);
        }
        if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) {
            if (refCnt == decrement) {
                deallocate();
                return true;
            }
            return false;
        }
    }
}

让我来构造一个出现该异常的例子,举例说明一下:
调用write()方法引起的引用计数异常
一个简单的服务端demo如下,该服务端实现的功能很简单,往前端输出一段json字符串:{"echo":"hello,netty"}

public class QueenServerHandler extends ChannelInboundHandlerAdapter {
    private static final String CONTENT_TYPE_JSON = "application/json;charset=UTF-8";
    private ChannelHandlerContext ctx;
    private static final ByteBuf BUF = Unpooled.copiedBuffer("{\"echo\":\"hello,netty\"}",CharsetUtil.UTF_8);
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        this.ctx = ctx;
        response(BUF);
    }
    private void response(ByteBuf buf){
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
        response.headers().add(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE_JSON);
        writeResponse(response);
    }
    private void writeResponse(Object response){
        ChannelFuture future = ctx.channel().writeAndFlush(response);
        future.addListener(ChannelFutureListener.CLOSE);
    }
}

该代码之后,在浏览器上发起请求,第一次系统将正常返回{"echo":"hello,netty"}的内容,从第二次开始请求时,服务端将抛出下面的异常,如图image8所示:

从异常抛出的信息:decrement: 1可以知道,应该是在调用release()方法时抛出的错。
从异常的堆栈中可以发现,是在调用write()方法时出现的错,在MessageToMessageEncoder.write()的方法中,发现了调用release的代码,如图image9所示:

所以到这里,异常出现的原因就很清楚了:
该ByteBuf对象是一个类变量:private static final ByteBuf BUF;,第一次调用release时已经成功将BUF对象回收了,之后再次调用release时,就会抛出IllegalReferenceCountException异常了。
知道原因之后,解决的方法就很简单了,有两种方法:

  • 1.将BUF对象改为局部变量,每次write的时候都创建一个新的BUF对象,之后每次再调用release就不会有问题了。
  • 2.每次使用时获取BUF的一个拷贝,用BUF.copy()来返回一个新的ByteBuf对象
### 如何在 Netty 中查看 ByteBuf 的引用计数Netty 中,`ByteBuf` 是一种用于处理字节缓冲区的核心组件。它通过引用计数机制来管理内存资源的分配与释放。当 `ByteBuf` 被释放后,可以通过调用其方法来检查当前的引用计数值。 以下是关于如何在 `ByteBuf` 释放后检查其引用计数的具体说明: #### 使用 `refCnt()` 方法获取引用计数 `ByteBuf` 提供了一个名为 `refCnt()` 的方法,该方法返回当前对象引用计数值[^1]。此方法可以用来验证 `ByteBuf` 是否已经被完全释放(即引用计数为零)。如果引用计数为零,则表示该对象已经不可用,并且任何尝试修改它的操作都会引发异常[^4]。 下面是一个简单的代码示例展示如何使用 `refCnt()` 来检查引用计数的状态: ```java import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; public class ByteBufRefCountExample { public static void main(String[] args) { // 创建一个新的 ByteBuf 实例 ByteBuf buf = Unpooled.buffer(); // 输出初始引用计数 System.out.println("Initial ref count: " + buf.refCnt()); // 写入一些数据到 Buffer 中 buf.writeBytes(new byte[]{0x68, 0x69}); // 检查并打印引用计数 System.out.println("After writing data, ref count: " + buf.refCnt()); // 释放 ByteBuf 对象 buf.release(); // 检查释放后的引用计数 System.out.println("After releasing, ref count: " + buf.refCnt()); } } ``` 上述程序执行的结果将是这样的: - 初始时,`ByteBuf` 的引用计数通常设置为 1; - 当调用了 `release()` 方法之后,引用计数变为 0;此时再次访问或修改已释放的对象将会触发非法状态错误。 需要注意的是,在实际应用开发过程中应当始终遵循良好的实践原则——确保每次创建新的 `ByteBuf` 都有对应的释放逻辑存在,从而避免潜在的内存泄漏问题[^3]。 #### 关于 AbstractReferenceCountedByteBuf 类的作用 为了支持更复杂的场景需求,Netty 设计了一套基于抽象基类 `AbstractReferenceCountedByteBuf` 的实现体系结构,其中定义了许多重要的功能特性,比如自动化的垃圾收集以及高效的缓存重用策略等[^2]。这些设计使得开发者能够更加方便地管理和控制网络通信中的二进制流传输过程。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值