Reference counted objects (참조 카운트된 객체)

2017. 5. 2. 17:49Java/Netty

네티를 프로젝트에 사용하기 위해 공부하면서 , 가장 중요한 내용이 바로 Reference counted object에 대한 이해인듯 싶다.
참조 카운트된 객체에 대한 내용에 대해서 해석을 해봤는데... 역시 오래간만에 해석을 하는터라.. 잘 안된다.
하지만 몇번이고 꼽씹어보면서, 읽어봐야 할 내용이다.



 Reference counted objects  (참조 카운트된 객체)

     네티 버전 4 이래로, 객체들의 라이프 주기는 참조 카운트에 의해 관리되므로,  
     네티가 객체 풀(또는 객체 할당자)에 참조 카운트(또는 확실한 객체들의 공유된 자원들)을 더 이상 사용하지 않는 즉시 리턴할수 있습니다.
     GC 과 참조 대기열들은  그런 효율적인 미도달 상태성(unreachability) 실시간 보장(guarantee)을 제공하지 않지만,
     참조 카운팅은 약간의 불편 비용에 대해 대체 메카니즘을 제공하고 있습니다. 
     ByteBuf은  allocation(할당) 과 deallocation(반환) 성능 향상을 시키기 위한 참조카운팅의 장점을 갖고 있는 가장 유명한 타입입니다.
     그리고 이 페이지는  어떻게 네티상에 참조계산이 ByteBuf를 사용해서 동작하는지를 설명합니다.

     Since Netty version 4, the life cycle of certain objects are managed by their reference counts,
     so that Netty can return them(or their shared resources) to an object pool (or an object allocator) as soon as it is not used anymore.
     Garbage collection and reference queues do not provide such efficient real-time guarantee of unreachability
     while reference-counting provides an alternative mechanism at the cost of slight inconvenience.
     ByteBuf is the most notable type which takes advantage of reference counting to improve the allocation and deallocation performance,
     and this page will explain how reference counting in Netty works using ByteBuf.


Basics of reference counting(참조계산의 기초)
    
     새로 참조카운팅된 객체의 초기 참조 카운트는 1 입니다.
     The initial reference count of a new reference-counted object is 1:
     

  ByteBuf buf = ctx.alloc().directBuffer();
  assert buf.refCnt() == 1;

     참조카운트된 객체를 해제할때, 해당 객체의 참조카운트는 1씩 감소됩니다.
     참조 카운트가 0에 다다르면,  참조카운트된 객체는 반환되거나, 이전 객체풀에 리턴되어 집니다.
     When you release the reference-counted object, its reference count is decreased by 1.
     If the reference count reaches at 0, the reference-counted object is dealocated or returned to the object pool it came from :
 
     assert buf.refCnt() == 1;
     //release() returns true only if the reference count becomes 0. (release() 는 참조 카운트가 0이 되었을때만, true 를 리턴합니다.)
     boolean destoryed = buf.release();
     assert destoryed;
     assert buf.refCnt() == 0;
 

Dangling reference. (참조 매달리기)
     
     참조 카운트가 0인 참조 카운트된 객체에 접근을 시도하는 경우 IllegalReferenceCountException 이 발생됩니다.
     Attempting to access the reference-counted  object whose reference count is 0 will trigger an IllegalReferenceCountException:

     assert buf.refCnt()  == 0 ;
     try{
          buf.writeLong(0xdeadbeef);
          throw new Error("should not reach here");
     }catch(IllegalReferenceCountException e){
          //Expected
     }


Increasing the reference count (참조 카운트 늘리기)
     
     참조 카운트는 아직 소멸되어지 않는 한 retain() 연산을 통해 증가될수 있습니다.  
     A reference count can also be incremented via the retain() operation as long as it is not destoryed yet:

     ByteBuf buf = ctx.alloc().directBuffer();
     assert buf.refCnt() == 1;

     buf.retain();
     assert buf.refCnt() ==2;

     boolean destoryed = buf.release();
     assert !destroyed;
     assert buf.refCnt() == 1;


Who destroys it? (객체를 소멸하는 건?)

대략적으로 기본 룰은 최종 참조 카운트된 객체에 접근하는 영역이 참조 카운트된 객체의 소멸에 대한 책임을 지고 있습니다. 
좀더 세부적으로는 :
The general rule of thumb is that the party who accesses a reference-counted object lastly is responsible for the destruction of the reference-counted object. More specifically:
 
- 하나의 컴포넌트가 다른 컨포넌트에 참조 카운트된 객체를 전달한다고 하면,
  보낸 컴포넌트는 일반적으로 그것을 반환할 필요가 없지만, 받는 컴포넌트에 결정을 미루게 됩니다.
- 하나의 컴포넌트는 참조 카운트된 객체를 소비하면, 그 객체에 더이상 아무것도 접근할수 없다는 것을 알수 있습니다.
  (예를 들면, 또 다른 컴포넌트에 참조를 전달하지 않습니다.), 컴포넌트는 그것을 소멸해야 합니다. 

- if a [sending] component is supposed to pass a reference-counted object to another [receiving] component,
  the sending component usually does not need to destroy it but defers that decision to the receiving component.
- if a component consumes a reference-counted object and knows nothing else will access it anymore
 (i.e... does not pass along a reference to yet another component), the componet should destory it.

예를 들면 아래와 같습니다:
Here is a  simple example :


public ByteBuf a(ByteBuf input){
               input.writeByte(42);
               return input;
          }

          public ByteBuf b(ByteBuf input){
               try{
                    output = input.alloc().directBuffer(input.readableBytes() + 1);
                    output.writeBytes(input);
                    output.writeByte(42);
                    return output;
               }finally{
                    input.release();
               }
          }

          public void c(ByteBuf input){
               System.out.println(input);
               input.release();
          }

          pulbic void main(){
               ...
               ByteBuf byf = ...;
               //This will print buf to System.out and destroy it.
               c(b(a(buf)));
               assert buf.refCnt() = = 0;
          }


          
Action(행동)Who should release?(누가 해제하나?)Who released?(해제된 건?)
1. main() creates bufbufmain()
2. main() calls a() with bufbufa()
3. a() returns buf merely.bufmain()
4. main() calls b() with bufbufb()
5. b() returns the copy of bufbufb()copymain()b() releases buf
6. main() calls c() with copycopyc()
7. c() swallows copycopyc()c() releases copy

Derived buffers (파생 버퍼)

     ByteBuf.duplicate(), ByteBuf.slice() 그리고 ByteBuf.order(ByteOrder)는 부모 버퍼의 메모리 영역을 공유하는 파생 버퍼를 생성합니다.
     파생버퍼는 자신의 참조 카운트를 갖고 있지 않고, 부모 버퍼의 참조 카운트를 공유합니다.
     ByteBuf.duplicate(), ByteBuf.slice() and ByteBuf.order(ByteOrder) create a derived buffer which shares the memory region of the parent buffer.
     A derived buffer does not have its own reference count but shares the reference count of the parent buffer.
     
     ByteBuf parent = ctx.alloc.directBuffer();
     ByteBuf derived = parent.duplicate();

     //creating a derived buffer does not increate the reference count.
     assert parent.refCnt()  == 1;
     assert derived.refCnt() == 1;

     대조적으로, ByteBuf.copy() 와 Bytebuf.readBytes(int) 는 파생버퍼가 아닙니다.
     할당되어 리턴된 ByteBuf 는 해제되어야 할 필요가 있을 것입니다.
     In contrast , ByteBuf.copy() and ByteBuf.readBytes(int) are not derived buffers.
     The returned ByteBuf is allocated will need to be released.


     기억할것은 부모버퍼와 그것에 파생 버퍼들은 동일한 참조 카운트를 공유하고, 파생버퍼가 증가될때마다 참조 카운트는 증가하지 않습니다. 
     그럼에도 불구하고, 여러분이 파생버퍼를 어플리케이션의 다른 컴포넌트로 전달할 것이라면, 가장먼저 retain()를 호출해야할 것입니다.
     Note that a parent buffer and its derived buffers share the same reference count,  and the reference count does not increase when a derived buffer is created.
     Therefore, if you are going to pass a derived buffer to other component of your application, you'll have to call retain() it first.

     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 interface  (ByteBufHolder 인터페이스)

     가끔, ByteBuf는 buffer holder에 의해 포함되는데, 예를 들면 DatagramPacket, HttpContent 그리고 WebSocketframe 입니다.
     이들 타입들은 ByteBufHolder 라는 공통 인터페이스를 확장합니다. 
     버퍼홀더는 포함하고있는 참조 카운트를 공유합니다. 파생버퍼와 마찬가지로 

     Sometimes, a ByteBuf is contained by a buffer holder, such as DatagramPacket, HttpContent and WebSocketframe.
     Those types extend a common interface  called ByteBufHolder. 
     A buffer holder shares the reference count of the buffer it contains, just like a  derived buffer.
     



Reference-counting in ChannelHandler - ChannelHandler 에서의 참조카운트

Inbound messages (인바운드 메시지) 

     이벤트 루프가 ByteBuf안에 데이터를 읽고, 데이터와 함께 channelRead() 이벤트를 동시에 발생시키면,
     해당 파이프 라인에서 버퍼를 해제하는 것은 ChannelHandler가 담당합니다.  
     그러므로 받은 데이터를 소비하는 핸들러는 해당 channelRead() 핸들러 메소드 내에 데이터에 release()을 호출해야 합니다.

     When an event loop reads data into a ByteBuf and triggers a channelRead() event with it,
     it is the responsibility of the ChannelHandler in the  corresponding pipeline to release the buffer.
     Therefore, the handler that consumes the received data should call release() on the data in its channelRead() handler method:

public void channelRead(ChannelHandlerContext ctx, Object msg){
     ByteBuf buf = (ByteBuf)msg;
     try{
          ...
     }finally{
          buf.release();
     }
}


이 문서내 "who destroys?" 섹션에서 설명했다시피, 여러분이 핸들러가 버퍼를 다음 핸들러에게 전달했다면, 여러분은 그 버퍼를 해제할 필요가 없습니다.
As explained in the "Who destroys?" section of this doucment, if your handler passes the buffer(or any reference-counted object) to the next handler,
you don't need to release it:


public void channelRead(ChannelHandlerContext ctx, Object msg){
     ByteBuf buf = (ByteBuf) msg;
     ...
     ctx.fireChannelRead(buf);
}

여기서, ByteBuf는 네티에서 유일한 참조 카운트된 타입이 아닙니다.
혹 여러분이 decoders에 의해 생성된  메시지를 다루고 있는 경우, 메시지가  참고 카운트된 것일 수도 있습니다. 
Note that ByteBuf isn't the only reference-counted type in Netty.
If you are  dealing  with the message generated by decoders, it  is very likely that the message is also reference-counted:

//Assuming your handler is placed next to 'HttpRequestDecoder'  (핸들러가 소비하는 것은 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() 를 사용 할수 있습니다.
if you are in doubt or you want to simplify releasing the messages, you can use ReferenceCountUtil.release();

public void channelRead(ChannelHandlerContext ctx, Object msg){
     try{
          ...
     }finally{
          ReferenceCountUtil.release();
     }
}


또는, 여러분은 받은 모든 메시지에 대해 ReferenceCountUtil.release(msg)를 호출하는 SimpleChannelHandler를 확장하는 것을 고려할수 있습니다.
Alternatively, you could consider extending SimpleChannelHandler which calls ReferenceCountUtil.release(msg) for all messages you receive.

Outbound messages (아웃바운드 메시지)

     인바운드 메시지들과는 다르게, 아웃바운드 메시지들은 어플리케이션에서 생성되고, 그들을 작성해서 보낸후에 wire에서 버퍼를 해제하는 것은 네티의 책임입니다.
     그러나, 여러분의 쓰기 요청을 가로채는 핸들러들은 어떤 매개 객체라도 정확하게 해제하는 것을 확실하게 해야 합니다.
     Unlike inbound messages, outbound messages are created by your application, and it is the reponsibility of Netty to release these after writing them out to the wire.
     However, the handlers that intercept your write requests  should make sure to release any intermediary object properly (e.g. encoders)


     //Simple-pass though
     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);
          }
     }
     

Troubleshooting buffer leaks (버퍼 누수 문제 해결)

     참조카운팅의 단점은 참조계산된 객체들을 누락하기가 쉽다는 것이다. 
     이유는 JVM은  네티가 구현하는 참조 카운팅에 대해 인식하지 못하기  때문에 , 참고 카운트가 0이 아니더라도 도달 할 수 없으면 , 자동으로 GC를 실행하기 때문입니다. 
     The disadvantage of reference counting is that is easy to leak the reference-counted objects.
     Because JVM is not aware of the reference counting Netty implements, it will automatically GC them once they become unreachable even if their reference counts are not zero.

     한번 GC된 객체는 재생되어 질수 없고, 따라서 가져온 풀에서 리턴될수도 없고, 따라서 메모리 누수가 생성될 것입니다.
     다행히도, 누수된것을 찾는것에 대한 어려움에도, 네티는 기본적으로 어플리케이션안에 누수가 있는지 확인하기 위해서, 약 1%의 버퍼 할당을 샘플링 합니다.
     An object once garbage collected cannot be resurrected, and  thus cannot be returned to the pool it came from and thus will produce memory leak.
     Fortunately, despite its difficulty of finding leaks, Netty will by default sample about 1 % of buffer allocations to check if there is a leak in your application.
    
     누수가 발생한 경우에, 아래와 같은 로그 메시지를 찾을수 있습니다.
     In case of leak, you will find the following log message:


LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable advanced leak reporting to find out where the leak occurred. To enable advanced leak reporting, specify the JVM option '-Dio.netty.leakDetectionLevel=advanced' or call ResourceLeakDetector.setLevel()

     위에 언급한 JVM 옵션을 가지고 어플리케이션 재실행하세요, 그땐 여러분은 누수된 버퍼가 접근했던 여러분의 어플리케이션 최근 위치를 볼수 있을 것입니다. 
     Relaunch your application with the JVM option mentioned above, then you'll see the recent locations of your application where the leaked buffer was accessed.

     아래 내용은 단위테스트로 부터 나온 누수 정보를 보여줍니다.
     The following output shows a leak from our unit test(XmlFrameDecorderTest.testDecoderWithXml());

Running io.netty.handler.codec.xml.XmlFrameDecoderTest
15:03:36.886 [main] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected.
Recent access records: 1
#1:
    io.netty.buffer.AdvancedLeakAwareByteBuf.toString(AdvancedLeakAwareByteBuf.java:697)
    io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithXml(XmlFrameDecoderTest.java:157)
    io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithTwoMessages(XmlFrameDecoderTest.java:133)
    ...

Created at:
    io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:55)
    io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:155)
    io.netty.buffer.UnpooledUnsafeDirectByteBuf.copy(UnpooledUnsafeDirectByteBuf.java:465)
    io.netty.buffer.WrappedByteBuf.copy(WrappedByteBuf.java:697)
    io.netty.buffer.AdvancedLeakAwareByteBuf.copy(AdvancedLeakAwareByteBuf.java:656)
    io.netty.handler.codec.xml.XmlFrameDecoder.extractFrame(XmlFrameDecoder.java:198)
    io.netty.handler.codec.xml.XmlFrameDecoder.decode(XmlFrameDecoder.java:174)
    io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:227)
    io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:140)
    io.netty.channel.ChannelHandlerInvokerUtil.invokeChannelReadNow(ChannelHandlerInvokerUtil.java:74)
    io.netty.channel.embedded.EmbeddedEventLoop.invokeChannelRead(EmbeddedEventLoop.java:142)
    io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:317)
    io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)
    io.netty.channel.embedded.EmbeddedChannel.writeInbound(EmbeddedChannel.java:176)
    io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithXml(XmlFrameDecoderTest.java:147)
    io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithTwoMessages(XmlFrameDecoderTest.java:133)
    ...

     네티 5나 그이상을 사용하면 추가정인 정보가 제공됩니다. 여러분이 누수 버퍼를 최근에 다룬것을 찾도록 도움을 줍니다.
     아래 예제는 EchoServerHandler#0 라는 이름의 핸들러에 의해 다뤄진 누수된 버퍼를 보여주고,
     그담 버퍼를 해제하는 것을 잊어버린 EchoServerHandler#0 라는 의미의 GC 되었다.
    
     If you use Netty 5 or above, an additional information is provided to help you find which handler the leaked buffer lastly.
     The following example shows that the leaked buffer was handled by the handler  whose name is EchoServerHandler#0 and then garbage-collected,
     which means  it is likely that EchoServerHandler#0 forgot to release the buffer.


12:05:24.374 [nioEventLoop-1-1] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected.
Recent access records: 2
#2:
    Hint: 'EchoServerHandler#0' will handle the message from this point.
    io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:329)
    io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)
    io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:133)
    io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
    io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
    io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
    io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
    java.lang.Thread.run(Thread.java:744)
#1:
    io.netty.buffer.AdvancedLeakAwareByteBuf.writeBytes(AdvancedLeakAwareByteBuf.java:589)
    io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:208)
    io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:125)
    io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
    io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
    io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
    io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
    java.lang.Thread.run(Thread.java:744)
Created at:
    io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:55)
    io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:155)
    io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:146)
    io.netty.buffer.AbstractByteBufAllocator.ioBuffer(AbstractByteBufAllocator.java:107)
    io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:123)
    io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
    io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
    io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
    io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
    java.lang.Thread.run(Thread.java:744)

Leak detection levels (누수 감지 레벨)

     4개의 누수 감지 레벨이 있습니다.
     There are currently 4 levels of leak detection:
     
     - DISABLED : disables leak detection completely. Not recommended.  (완벽하게 누수 감지 감지하지 않음. 추천하지 않습니다.)
     - SIMPLE : tells if there is a leak or note for 1% of buffers. Default.    (버퍼의 1%에 누수 혹은 메모가 있는지 알려줌, 기본)
     - ADVANCED : tells where the leaked buffer was accessed for 1 % of buffers. (버퍼의 1%에 누수된 버퍼가 접근했던 곳을 알려줌)
     - PARANOID - Same with ADVANACED  except that it's for every single buffer.  (ADVANCED 와 같은데, 모든 싱글 버퍼에 대해서 감지하는 것을 제외하고..)
                         Useful for automated testing phase. You could fail the build if the build output contains 'LEAK:', 
                          (자동화 테스트 구문에서 유용하지요.  build output에 LEAK을 포함하면, 빌드를 실패할수 있습니다.)
     
    JVM 옵션으로 누수 감지 레벨을 분류할수 있답니다. -
     You can specify the leak detection level as a JVM option - Dio.netty.leakDetectionLevel

java -Dio.netty.leakDetectionLevel=advanced ...

Best practices to avoid leaks (누수를 피하는 최고의 방법)
     
     - 단위테스트와 통합테스트를 PARANOID 누수 감지 레벨, SIMPLE 레벨에서 돌려라.
     -  완전한 클러스터에 롤링 아웃전에 어플리케이션을 SIMPLE 레벨에서 어느정도 긴 시간 누수가 있는지 보기 위해 Canary해라. 
     - 누수가 있다면,ADVANECE 레벨에서 다시  어디에서 누수가 있는지에 대한 위치에 대한 힌트를 얻기위해 Canary해라
     - 완전한 클러스터에 누수를 갖고 있는 어플리케이션을 배포하지 마라.

     - Run your unit tests and integration tests at PARANOID leak detection level, as well as at SIMPLE level.
     - Canary your application before rolling out to the entire cluster at SIMPLE level for a reasonably long time to see if there's leak.
     - if there is a leak, canary again at ADVANCED level to get some hints about where the leak is coming from
     - Do not deploy an application with a leak to the entire cluster.

Fixing leaks in unit tests (단위테스트에서 누수 고치기)

     단위테스트에서 버퍼 혹은 메시지를 해제하는 것을 잊는 것은 매우 쉽다.
     누수 경고를 생성할 것이지만, 어플리케이션이 필연적으로 누수를 가지고 있다라는 의미는 아니다.
     모든 버퍼를 해제 하기 위해 try-finally 블럭을 가진 단위 테스트를 래핑하는 대신, ReferenceCountUnit.releaseLater() 유틸 메소드를 사용할 수 있습니다.
     it is very easy to forget to release a buffer or a message in a unit test.
     It will generate a  leak warning, but it does not necessarily mean that your application has a leak.
     Instead of wrapping your unit tests with try-finally blocks to release all buffers, you can use ReferenceCountUnit.releaseLater() utility method:


import static io.netty.util.ReferenceCountUtil.*;

@Testpublic void testSomething() throws Exception {
    // ReferenceCountUtil.releaseLater() will keep the reference of buf,
    // and then release it when the test thread is terminated.
    ByteBuf buf = releaseLater(Unpooled.directBuffer(512));
    ...
}



The END