netty的異常分析 IllegalReferenceCountException refCnt: 0


netty的異常 IllegalReferenceCountException refCnt: 0

 

這是因為Netty有引用計數器的原因,自從Netty 4開始,對象的生命周期由它們的引用計數(reference counts)管理,而不是由垃圾收集器(garbage collector)管理了。ByteBuf是最值得注意的,它使用了引用計數來改進分配內存和釋放內存的性能。

在我們創建ByteBuf對象后,它的引用計數是1,當你釋放(release)引用計數對象時,它的引用計數減1,如果引用計數為0,這個引用計數對象會被釋放(deallocate),並返回對象池。

當嘗試訪問引用計數為0的引用計數對象會拋出IllegalReferenceCountException異常:

/**
 * 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 && refCnt() == 0) {
        throw new IllegalReferenceCountException(0);
    }
}

  

這個問題產生的最要原因是在第一次發送完心跳請求后,ctx.write 等一系列方法調用了ByteBuf的release方法,將其引用計數減為了0 導致的:

 

我們主要看其方法棧中調用信息,得到一個結論,是每次調用ctx.write/writeAndFlush, pipeline.write/writeAndFlush , 等一系列方法時,被封裝的ByteBuf對象的引用計數會減一。導致第二次使用同樣對象的包裝對象時,出現引用計數的問題。

 

可以調用 echoMsg.refCnt(); 來獲取當前引用計數值. 在 ctx.write(...) 前后加一行打印, 就可以發現, ctx.write(...) 完之后, 引用計數減少了1.

 

解決

  • 如果不想創建新的數據, 則可以直接在原對象里調用 echoMsg.retain() 進行引用計數加1.例如:
    @Override
    protected void channelRead0(final ChannelHandlerContext ctx, final HttpContent msg) {
        System.out.println("收到" + msg);
        ByteBuf echoMsg = msg.content();
        echoMsg.retain();
        System.out.println(new String(ByteBufUtil.getBytes(echoMsg)));
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, echoMsg);
        response.headers().set("Content-Type", "text/plain");
        ctx.write(response).addListener(ChannelFutureListener.CLOSE);
    }

  

即上面的 echoMsg.retain() 方法.

  • 構造 response 對象時, 不要復用 echoMsg 對象, 例如:
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(echoMsg));

  

即, 使用 Unpooled.copiedBuffer(...) 來復制多一份內存數據~

  • 直接使用 ChannelInboundHandlerAdapter 自動手動處理釋放, 以免像 SimpleChannelInboundHandler 那樣導致多次釋放引用計數對象~
package hello.in;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
public class EchoHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
        if (msg instanceof HttpContent) {
            manual(ctx, (HttpContent) msg);
        }
    }
    protected void manual(final ChannelHandlerContext ctx, final HttpContent msg) {
        System.out.println("收到" + msg);
        ByteBuf echoMsg = msg.content();
        System.out.println(new String(ByteBufUtil.getBytes(echoMsg)));
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, echoMsg);
        response.headers().set("Content-Type", "text/plain");
        ctx.write(response).addListener(ChannelFutureListener.CLOSE);
    }
    @Override
    public void channelReadComplete(final ChannelHandlerContext ctx) {
        ctx.flush();
    }
    @Override
    public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM