因项目须要,须要了解 Netty 这款号称 "高性能Java网络编程" 框架。拿起一本《Netty In Action》开始研究,在第2章的例子中,发现 Echo 服务端使用的ChannelHandler是 ChannelInboundHandlerAdapter ,而 Echo 客户端使用的倒是 SimpleChannelInboundHandler 。一脸茫然,不知所措,只能点进去看各自的实现原理。java
首先看到的是 SimpleChannelInboundHandler 继承自 ChannelInboundHandlerAdapter。算法
public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter{ ... }
有图有真相:编程
既然是继承关系,也就是说,"你有的我也有,你没有的我还有。" 那么 SimpleChannelInboundHandler 里面确定重写或者新增了 ChannelInboundHandlerAdapter 里面的方法功能 - channelRead0 和 channelRead()。promise
protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;
这里只提供了一个模板,做用是把处理逻辑不变的内容写好在 channelRead(ctx,msg) 中,而且在里面调用 channelRead0 ,这样变化的内容经过抽象方法实现传递到子类中去了(在Netty5中channelRead0已被重命名为messageReceived)。网络
@Override// 继承了 ChannelInboundHandlerAdapter#channelRead 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);// 释放资源(保存消息的ByteBuf) } } }
为何这样作?看看《Netty In Action》的原话:框架
在客户端,当 channelRead0() 方法完成时,你已经有了传入消息,而且已经处理完它了。当该方法返回时,SimpleChannelInboundHandler负责释放指向保存该消息的ByteBuf的内存引用。异步
那为何服务端不须要这样处理呢?ide
在EchoServerHandler中,你仍然须要将传入消息回送给发送者,而 write() 操做是异步的,直到 channelRead() 方法返回后可能仍然没有完成。为此,EchoServerHandler扩展了 ChannelInboundHandlerAdapter ,其在这个时间点上不会释放消息。性能
啥意思,个人理解是 ChannelInboundHandlerAdapter 不会像 SimpleChannelInboundHandler 同样在 channelRead() 里面释放资源,而是在收到消息处理完成的事件时,才会释放资源,看下面的代码就能理解了。spa
EchoServerHandler#channelReadComplete,这是一个EchoServer小例子:
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { //将未决消息冲刷到远程节点,而且关闭该 Channel ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) .addListener(ChannelFutureListener.CLOSE); }
消息在 channelReadComplete() 方法中,当 writeAndFlush() 方法被调用时才被释放,咱们点进去源码验证一下:
AbstractChannelHandlerContext#writeAndFlush,以下所示,最后的资源是在 writeAndFlush() 中被释放的。
public ChannelFuture writeAndFlush(Object msg) { return writeAndFlush(msg, newPromise());// 跳转到另一个重载的方法中 } public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { if (msg == null) {// msg不能为空 throw new NullPointerException("msg"); } if (isNotValidPromise(promise, true)) { ReferenceCountUtil.release(msg);// 释放资源(保存消息的ByteBuf) // cancelled return promise; } write(msg, true, promise);// 异步写操做 return promise; }
上面的源码中,最后资源是经过 ReferenceCountUtil 来释放的。也就是说,当咱们须要释放ByteBuf相关内存的时候,也能够使用 ReferenceCountUtil#release()。
ReferenceCountUtil 底层实现是 ReferenceCounted ,当新的对象初始化的时候计数为1,retain() 方法被调用时引用计数加1,release()方法被调用时引用计数减1,当计数减小到0的时候会被显示清除,再次访问被清除的对象会出现访问冲突(这里想起了JVM判断对象是否存活的引用计数算法)。
ReferenceCountUtil#release:
public static boolean release(Object msg) { if (msg instanceof ReferenceCounted) { return ((ReferenceCounted) msg).release();// Decreases the reference count by 1 } return false; }