Netty引用计数

Posted 逅弈逐码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty引用计数相关的知识,希望对你有一定的参考价值。

Netty中实现引用计数的接口是 ReferenceCounted,一个实现了 ReferenceCounted接口的类就具备了引用计数的能力。 当一个引用计数的对象被初始化时, 引用计数器被初始化为1。 ReferenceCounted接口中两个主要的方法:

retain()方法对引用计数加1,release()方法对引用计数减1。

如果引用计数被减到0,则这个对象就将被显式的回收,此时再来访问该对象则会抛出 IllegalReferenceCountException异常。

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

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

Netty引用计数

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

Netty引用计数

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

Netty引用计数

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

Netty引用计数

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


Netty引用计数


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

 
   
   
 
  1. public IllegalReferenceCountException(int refCnt, int increment) {

  2.    this("refCnt: " + refCnt + ", " + (increment > 0? "increment: " + increment : "decrement: " + -increment));

  3. }

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

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

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

  • 在retain方法中

 
   
   
 
  1. private ReferenceCounted retain0(int increment) {

  2.    for (;;) {

  3.        int refCnt = this.refCnt;

  4.        final int nextCnt = refCnt + increment;

  5.        // 当nextCnt<=increment时,表面refCnt==0,说明该对象已经被回收了

  6.        // 若此时再来增加引用计数的值,则抛出非法引用的异常

  7.        if (nextCnt <= increment) {

  8.            throw new IllegalReferenceCountException(refCnt, increment);

  9.        }

  10.        if (refCntUpdater.compareAndSet(this, refCnt, nextCnt)) {

  11.            break;

  12.        }

  13.    }

  14.    return this;

  15. }

  • 在release方法中

 
   
   
 
  1. private boolean release0(int decrement) {

  2.    for (;;) {

  3.        int refCnt = this.refCnt;

  4.        // 由于decrement最小为1,要满足refCnt<1成立,则refCnt==0,则说明该对象已经被回收了

  5.        // 若此时再来减少引用计数的值,则抛出非法引用的异常

  6.        if (refCnt < decrement) {

  7.            throw new IllegalReferenceCountException(refCnt, -decrement);

  8.        }

  9.        if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) {

  10.            if (refCnt == decrement) {

  11.                deallocate();

  12.                return true;

  13.            }

  14.            return false;

  15.        }

  16.    }

  17. }

让我来构造一个出现该异常的例子,举例说明一下:

调用write()方法引起的引用计数异常

一个简单的服务端demo如下,该服务端实现的功能很简单,往前端输出一段json字符串: {"echo":"hello,netty"}

 
   
   
 
  1. public class QueenServerHandler extends ChannelInboundHandlerAdapter {

  2.    private static final String CONTENT_TYPE_JSON = "application/json;charset=UTF-8";

  3.    private ChannelHandlerContext ctx;

  4.    private static final ByteBuf BUF = Unpooled.copiedBuffer("{\"echo\":\"hello,netty\"}",CharsetUtil.UTF_8);

  5.    @Override

  6.    public void channelRead(ChannelHandlerContext ctx, Object msg) {

  7.        this.ctx = ctx;

  8.        response(BUF);

  9.    }

  10.    private void response(ByteBuf buf){

  11.        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);

  12.        response.headers().add(HttpHeaderNames.CONTENT_TYPE, CONTENT_TYPE_JSON);

  13.        writeResponse(response);

  14.    }

  15.    private void writeResponse(Object response){

  16.        ChannelFuture future = ctx.channel().writeAndFlush(response);

  17.        future.addListener(ChannelFutureListener.CLOSE);

  18.    }

  19. }

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

Netty引用计数

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

所以到这里,异常出现的原因就很清楚了: 该ByteBuf对象是一个类变量: privatestaticfinalByteBufBUF;,第一次调用release时已经成功将BUF对象回收了,之后再次调用release时,就会抛出 IllegalReferenceCountException异常了。 指定原因之后,解决的方法也很简单,将BUF对象改为局部变量,每次write的时候都创建一个新的BUF对象,之后每次再调用release就不会有问题了。



以上是关于Netty引用计数的主要内容,如果未能解决你的问题,请参考以下文章

内存管理笔记

netty系列之:JVM中的Reference count原来netty中也有

Netty之内存泄露

关于此代码的引用计数的问题

感悟优化——Netty对JDK缓冲区的内存池零拷贝改造

Netty-源码分析ByteBuf-readSlice和readRetainedSlice使用细节