以下三种情况下对于ByteBuf的回收策略:
- ChannelOutboundHandler中write的ByteBuf,在pipeline中的tail handler将负责释放,详见AbstractChannelHandlerContext中的如下函数
private void write(Object msg, boolean flush, ChannelPromise promise) { ObjectUtil.checkNotNull(msg, "msg"); try { if (isNotValidPromise(promise, true)) { ReferenceCountUtil.release(msg); // cancelled return; } } catch (RuntimeException e) { ReferenceCountUtil.release(msg); throw e; } final AbstractChannelHandlerContext next = findContextOutbound(flush ? (MASK_WRITE | MASK_FLUSH) : MASK_WRITE); final Object m = pipeline.touch(msg, next); EventExecutor executor = next.executor(); if (executor.inEventLoop()) { if (flush) { next.invokeWriteAndFlush(m, promise); } else { next.invokeWrite(m, promise); } } else { final WriteTask task = WriteTask.newInstance(next, m, promise, flush); if (!safeExecute(executor, task, promise, m, !flush)) { // We failed to submit the WriteTask. We need to cancel it so we decrement the pending bytes // and put it back in the Recycler for re-use later. // // See https://github.com/netty/netty/issues/8343. task.cancel(); } } }
- ChannelInboundHandlerAdapter中接收到的ByteBuf,由最后一个接收到ByteBuf的handler负责释放,例如ByteToMessageDecoder处理过后的ByteBuf,由于向下游传递的类型不为ByteBuf,在ByteToMessageDecoder中即进行释放
final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { decodeState = STATE_CALLING_CHILD_DECODE; try { decode(ctx, in, out); } finally { boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING; decodeState = STATE_INIT; if (removePending) { fireChannelRead(ctx, out, out.size()); out.clear(); handlerRemoved(ctx); } } } @Override public final void handlerRemoved(ChannelHandlerContext ctx) throws Exception { if (decodeState == STATE_CALLING_CHILD_DECODE) { decodeState = STATE_HANDLER_REMOVED_PENDING; return; } ByteBuf buf = cumulation; if (buf != null) { // Directly set this to null so we are sure we not access it in any other method here anymore. cumulation = null; numReads = 0; int readable = buf.readableBytes(); if (readable > 0) { ctx.fireChannelRead(buf); ctx.fireChannelReadComplete(); } else { buf.release(); } } handlerRemoved0(ctx); }
-
SimpleChannelInboundHandler只对ByteBuf类型的数据进行回收
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { boolean release = true; try { if (acceptInboundMessage(msg)) { @SuppressWarnings("unchecked") I imsg = (I) msg; channelRead0(ctx, imsg); } else { release = false; ctx.fireChannelRead(msg); } } finally { if (autoRelease && release) { ReferenceCountUtil.release(msg); } } } public static boolean release(Object msg) { if (msg instanceof ReferenceCounted) { return ((ReferenceCounted) msg).release(); } return false; }
由此做出如下总结:
- 向外写数据时使用ByteBuf,保证能够在最后一个ChannelOutboundHandler时被回收,因为ReferenceCountUtil.release(msg)只回收实现了ReferenceCounted的类
- 业务处理handler使用非ByteBuf类型作为输入时,需要保证之前的ChannelInboundHandler将回收接收到的ByteBuf
- ByteToMessageDecoder会回收ByteBuf,将生成的消息向下游传递
- 如果自行申请了ByteBuf,需要在使用完成后手动回收
最佳实践
- 使用ByteToMessageDecoder + SimpleChannelInboundHandler处理写入消息
- 使用ByteBuf类型向外输出消息
- 尽量避免手动申请ByteBuf