因項目須要,須要瞭解 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; }