netty的異常分析 IllegalReferenceCountException refCnt: 0

netty的異常 IllegalReferenceCountException refCnt: 0java

 

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

在咱們建立ByteBuf對象後,它的引用計數是1,當你釋放(release)引用計數對象時,它的引用計數減1,若是引用計數爲0,這個引用計數對象會被釋放(deallocate),並返回對象池。性能

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

/**
 * 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 致使的:netty

 

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

 

能夠調用 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() 方法.blog

  • 構造 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();
    }
}
相關文章
相關標籤/搜索