Netty的引用计数对象
從Netty 4起,對象的生命周期由它們的引用計數來管理,因此,一旦對象不再被引用后,Netty 會將它(或它共享的資源)歸還到對象池(或對象分配器)。在垃圾回收和引用隊列不能保證這么有效、實時的不可達性檢測的情況下,引用計數以犧牲輕微的便利性為代價,提供了另一種可選的解決方案。 最值得注意的類型是ByteBuf,它正是利用了引用計數來提升內存分配和釋放的性能。
一、引用計數基本原理
一個新創建的引用計數對象的初始引用計數是1。
ByteBuf buf = ctx.alloc().directbuffer(); assert buf.refCnt() == 1;當你釋放掉引用計數對象,它的引用次數減1.如果一個對象的引用計數到達0,該對象就會被 釋放或者歸還到創建它的對象池。
assert buf.refCnt() == 1; // release() returns true only if the reference count becomes 0. boolean destroyed = buf.release(); assert destroyed; assert buf.refCnt() == 0;訪問引用計數為0的引用計數對象會觸發一次IllegalReferenceCountException:
assert buf.refCnt() == 0; try { buf.writeLong(0xdeadbeef); throw new Error("should not reach here"); } catch (IllegalReferenceCountExeception e) { // Expected }只要引用計數對象未被銷毀,就可以通過調用retain()方法來增加引用次數:
ByteBuf buf = ctx.alloc().directBuffer(); assert buf.refCnt() == 1; buf.retain(); assert buf.refCnt() == 2; boolean destroyed = buf.release(); assert !destroyed; assert buf.refCnt() == 1;一般的原則是,最后訪問引用計數對象的部分負責對象的銷毀。更具體地來說:
- 如果一個[發送]組件要傳遞一個引用計數對象到另一個[接收]組件,發送組件通常不需要 負責去銷毀對象,而是將這個銷毀的任務推延到接收組件
- 如果一個組件消費了一個引用計數對象,并且不知道誰會再訪問它(例如,不會再將引用 發送到另一個組件),那么,這個組件負責銷毀工作
這里有一個簡單的示例:
二、子緩沖區(Derived buffers)
調用ByteBuf.duplicate(),ByteBuf.slice()和ByteBuf.order(ByteOrder)三個方法, 會創建一個子緩沖區,子緩沖區共享父緩沖區的內存區域。子緩沖區沒有自己的引用計數,而是共享父緩沖區的引用計數。
ByteBuf parent = ctx.alloc().directBuffer(); ByteBuf derived = parent.duplicate(); // Creating a derived buffer does not increase the reference count. assert parent.refCnt() == 1; assert derived.refCnt() == 1;但是,調用ByteBuf.copy()和ByteBuf.readBytes(int)創建的并不是子緩沖區,返回的 ByteBuf緩沖區是需要被釋放的。 需要注意,父緩沖區和它的子緩沖區共享引用計數,創建子緩沖區并不會增加引用計數。 因此,當你將子緩沖區傳到應用中的其他組件,必須先調用retain()。
ByteBuf parent = ctx.alloc().directBuffer(512); parent.writeBytes(...); try {while (parent.isReadable(16)) {ByteBuf derived = parent.readSlice(16);derived.retain();process(derived);} } finally {parent.release(); } ... public void process(ByteBuf buf) {...buf.release(); }ByteBufHolder接口
有時候,ByteBuf被緩沖區容器(buffer holder)持有,像DatagramPacket、HttpComponent和WebSocketFrame。 這些類型都繼承自一個通用接口,叫做ByteBufHolder。 緩沖區容器(buffer holder)共享它持有的緩沖區的引用計數,和子緩沖區一樣。
Channel-handler中的引用計數
- 入口消息
當一個事件循環(event loop)讀取數據并寫入到ByteBuf,在觸發一次channelRead()事件后,應該由對應pipeline的 ChannelHandler負責去釋放緩沖區的內存。因此,消費接收數據的handler應該在它channelRead()方法中調用數據的 release()方法。
public void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf buf = (ByteBuf) msg;try {...} finally {buf.release();}}在“誰負責銷毀”一節中我們提到,如果你的handler將緩沖區(或者其他任何引用計數對象)傳遞到下一個handler, 那么你不需要負責去釋放。
public void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf buf = (ByteBuf) msg;...ctx.fireChannelRead(buf); }需要注意的是,ByteBuf并不是Netty中唯一的引用計數類型。如果你在與解碼程序(decoder)生成的消息打交道,這些消息一樣可能 是引用計數的。
/ Assuming your handler is placed next to `HttpRequestDecoder` public void channelRead(ChannelHandlerContext ctx, Object msg) {if (msg instanceof HttpRequest) {HttpRequest req = (HttpRequest) msg;...}if (msg instanceof HttpContent) {HttpContent content = (HttpContent) msg;try {...} finally {content.release();}} }如果你有疑慮,或者你想簡化釋放消息內存的過程,你可以使用ReferenceCountUtil.release():
public void channelRead(ChannelHandlerContext ctx, Object msg) {try {...} finally {ReferenceCountUtil.release(msg);} }同樣地,你可以考慮繼承SimpleChannelHandler,它會幫你調用ReferenceCountUtil.release()釋放所有 你接收到的消息內存。
- 出口消息
與入口消息不同的是,出口消息是在你的應用中創建的,由Netty負責在將消息發送出去后釋放掉。但是,如果你 有攔截寫請求的handler程序,則需要保證正確釋放中間對象(例如,編碼程序)。
public void write(ChannelHandlerContext ctx, Object message, ChannelPromise promise) {System.err.println("Writing: " + message);ctx.write(message, promise); } // Transformation public void write(ChannelHandlerContext ctx, Object message, ChannelPromise promise) {if (message instanceof HttpContent) {// Transform HttpContent to ByteBuf.HttpContent content = (HttpContent) message;try {ByteBuf transformed = ctx.alloc().buffer();....ctx.write(transformed, promise);} finally {content.release();}} else {// Pass non-HttpContent through.ctx.write(message, promise);} }三、內存泄漏
引用計數的缺點是,引用計數對象容易發生泄露。因為JVM并不知道Netty的引用計數實現,當引用計數對象不可達時,JVM就會將它們GC掉,即時此時它們的引用計數并不為0。一旦對象被GC就不能再訪問,也就不能 歸還到緩沖池,所以會導致內存泄露。 慶幸的是,盡管發現內存泄露很難,但是Netty會對分配的緩沖區的1%進行采樣,來檢查你的應用中是否存在內存 泄露。
文章轉自
總結
以上是生活随笔為你收集整理的Netty的引用计数对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据产品经理为什么吃香?
- 下一篇: 五、Netty核心组件