【Netty官方文档翻译】引用计数对象(reference counted objects)

简介: 【Netty官方文档翻译】引用计数对象(reference counted objects)

原文出处:http://netty.io/wiki/reference-counted-objects.html

原文地址可能有变,且内容可能发生变化。

如果转载请注明出处,谢谢合作^_^。

 

自从Netty 4开始,对象的生命周期由它们的引用计数(reference counts)管理,而不是由垃圾收集器(garbage collector)管理了。ByteBuf是最值得注意的,它使用了引用计数来改进分配内存和释放内存的性能。

 

基本的引用计数

 

每个对象的初始计数为1:

 

Java代码  

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

 

 

当你释放(release)引用计数对象时,它的引用计数减1.如果引用计数为0,这个引用计数对象会被释放(deallocate),并返回对象池。

 

Java代码  

  1. assert buf.refCnt() == 1;  
  2. // release() returns true only if the reference count becomes 0.  
  3. boolean destroyed = buf.release();  
  4. assert destroyed;  
  5. assert buf.refCnt() == 0;  

 

 

悬垂(dangling)引用

 

尝试访问引用计数为0的引用计数对象会抛出IllegalReferenceCountException异常:

 

Java代码  

  1. assert buf.refCnt() == 0;  
  2. try {  
  3.  buf.writeLong(0xdeadbeef);  
  4.  thrownew Error("should not reach here");  
  5. } catch (IllegalReferenceCountExeception e) {  
  6.  // Expected  
  7. }  

 

 

增加引用计数

 

可通过retain()操作来增加引用计数,前提是此引用计数对象未被销毁:

(译者注:跟未使用ARC的objective-c好像)

 

Java代码  

  1. ByteBuf buf = ctx.alloc().directBuffer();  
  2. assert buf.refCnt() == 1;  
  3.  
  4. buf.retain();  
  5. assert buf.refCnt() == 2;  
  6.  
  7. boolean destroyed = buf.release();  
  8. assert !destroyed;  
  9. assert buf.refCnt() == 1;  

 

谁来销毁(destroy)

 

通常的经验法则是谁最后访问(access)了引用计数对象,谁就负责销毁(destruction)它。具体来说是以下两点:

  • 如果组件(component)A把一个引用计数对象传给另一个组件B,那么组件A通常不需要销毁对象,而是把决定权交给组件B。
  • 如果一个组件不再访问一个引用计数对象了,那么这个组件负责销毁它。

 

下面是一个简单的例子:

 

Java代码  

  1. public ByteBuf a(ByteBuf input) {  
  2.    input.writeByte(42);  
  3.    return input;  
  4. }  
  5.  
  6. public ByteBuf b(ByteBuf input) {  
  7.    try {  
  8.        output = input.alloc().directBuffer(input.readableBytes() + 1);  
  9.        output.writeBytes(input);  
  10.        output.writeByte(42);  
  11.        return output;  
  12.    } finally {  
  13.        input.release();  
  14.    }  
  15. }  
  16.  
  17. publicvoid c(ByteBuf input) {  
  18.    System.out.println(input);  
  19.    input.release();  
  20. }  
  21.  
  22. publicvoid main() {  
  23.    ...  
  24.    ByteBuf buf = ...;  
  25.    // This will print buf to System.out and destroy it.  
  26.    c(b(a(buf)));  
  27.    assert buf.refCnt() == 0;  
  28. }  

 

 

行为(Action)                          谁来释放(Who should release)?   谁释放了(Who released)?

1. main()创建了buf                    buf→main()

2. buf由main()传给了a()            buf→a()

3. a()仅仅返回了buf                   buf→main()

4. buf由main()传给了b()            buf→b()

5. b()返回了buf的拷贝               buf→b(), copy→main()                       b()释放了buf

6. 拷贝由main()传给了c()          copy→c()

7. c()消耗(swallow)了拷贝     copy→c()                                           c()释放了拷贝

 

子缓冲(Derived buffers)

 

ByteBuf.duplicate(), ByteBuf.slice()和ByteBuf.order(ByteOrder)创建了子缓冲,这些缓存共享了它们的父缓冲(parent buffer)的一部分内存。子缓冲没有自己的引用计数,而是共享父缓冲的引用计数。

Java代码  

  1. ByteBuf parent = ctx.alloc().directBuffer();  
  2. ByteBuf derived = parent.duplicate();  
  3.  
  4. // Creating a derived buffer does not increase the reference count.  
  5. assert parent.refCnt() == 1;  
  6. assert derived.refCnt() == 1;  

 

注意父缓冲和它的子缓冲共享同样的引用计数,当创建子缓冲时并不会增加对象的引用计数。因此,如果你要传递(pass)一个子缓冲给你的程序中的其他组件的话,你得先调用retain()。

 

Java代码  

  1. ByteBuf parent = ctx.alloc().directBuffer(512);  
  2. parent.writeBytes(...);  
  3.  
  4. try {  
  5.    while (parent.isReadable(16)) {  
  6.        ByteBuf derived = parent.readSlice(16);  
  7.        derived.retain();  
  8.        process(derived);  
  9.    }  
  10. } finally {  
  11.    parent.release();  
  12. }  
  13. ...  
  14.  
  15. publicvoid process(ByteBuf buf) {  
  16.    ...  
  17.    buf.release();  
  18. }  

 

ByteBufHolder接口

 

有时候,一个ByteBuf被一个buffer holder持有,诸如DatagramPacket, HttpContent,和WebSocketframe。它们都扩展了一个公共接口,ByteBufHolder。

 

一个buffer holder共享它所持有的引用计数,如同子缓冲一样。

 

ChannelHandler中的引用计数

 

Inbound消息(messages)

 

当一个事件循环(event loop)读入了数据,用读入的数据创建了ByteBuf,并用这个ByteBuf触发了一个channelRead()事件时,那么管道(pipeline)中相应的ChannelHandler就负责释放这个buffer。因此,处理接收到的数据的handler应该在它的channelRead()中调用buffer的release()。

Java代码  

  1. publicvoid channelRead(ChannelHandlerContext ctx, Object msg) {  
  2.    ByteBuf buf = (ByteBuf) msg;  
  3.    try {  
  4.        ...  
  5.    } finally {  
  6.        buf.release();  
  7.    }  
  8. }  

 

如同在本文档中的“谁来销毁”一节所解释的那样,如果你的handler传递了缓存(或任何引用计数对象)到下一个handler,你就不需要释放它:

Java代码  

  1. publicvoid channelRead(ChannelHandlerContext ctx, Object msg) {  
  2.    ByteBuf buf = (ByteBuf) msg;  
  3.    ...  
  4.    ctx.fireChannelRead(buf);  
  5. }  

 

注意ByteBuf不是Netty中唯一一种引用计数对象。由解码器(decoder)生成的消息(messages)对象,这些对象很可能也是引用计数对象:

Java代码  

  1. // Assuming your handler is placed next to `HttpRequestDecoder`  
  2. publicvoid channelRead(ChannelHandlerContext ctx, Object msg) {  
  3.    if (msg instanceof HttpRequest) {  
  4.        HttpRequest req = (HttpRequest) msg;  
  5.        ...  
  6.    }  
  7.    if (msg instanceof HttpContent) {  
  8.        HttpContent content = (HttpContent) msg;  
  9.        try {  
  10.            ...  
  11.        } finally {  
  12.            content.release();  
  13.        }  
  14.    }  
  15. }  

 

如果你抱有疑问,或者你想简化这些释放消息的工作,你可以使用ReferenceCountUtil.release():

Java代码  

  1. publicvoid channelRead(ChannelHandlerContext ctx, Object msg) {  
  2.    try {  
  3.        ...  
  4.    } finally {  
  5.        ReferenceCountUtil.release(msg);  
  6.    }  
  7. }  

 

还有一种选择,你可以考虑继承SimpleChannelHandler,它在所有接收消息的地方都调用了ReferenceCountUtil.release(msg)。

 

Outbound消息(messages)

 

与inbound消息不同,你的程序所创建的消息对象,由Netty负责释放,释放的时机是在这些消息被发送到网络之后。但是,在发送消息的过程中,如果有handler截获(intercept)了你的发送请求,并创建了一些中间对象,则这些handler要确保正确释放这些中间对象。比如编码器(encoder)。

 

Java代码  

  1. // Simple-pass through  
  2. publicvoid write(ChannelHandlerContext ctx, Object message, ChannelPromise promise) {  
  3.    System.err.println("Writing: " + message);  
  4.    ctx.write(message, promise);  
  5. }  
  6.  
  7. // Transformation  
  8. publicvoid write(ChannelHandlerContext ctx, Object message, ChannelPromise promise) {  
  9.    if (message instanceof HttpContent) {  
  10.        // Transform HttpContent to ByteBuf.  
  11.        HttpContent content = (HttpContent) message;  
  12.        try {  
  13.            ByteBuf transformed = ctx.alloc().buffer();  
  14.            ....  
  15.            ctx.write(transformed, promise);  
  16.        } finally {  
  17.            content.release();  
  18.        }  
  19.    } else {  
  20.        // Pass non-HttpContent through.  
  21.        ctx.write(message, promise);  
  22.    }  
  23. }  

 

解决(troubleshooting)buffer泄露

 

引用计数的缺点是容易发生泄露。因为JVM并不知道Netty实现的引用计数的存在,一旦某些对象不可达(unreachable)就会被自动GC掉,即使这些对象的引用计数不为0。被GC掉的对象就不可用了,因此这些对象也就不能回到对象池中,或者产生内存泄露。

 

 

幸运的是,尽管要找到泄露很困难,但Netty提供了一种方案来帮助发现泄露,此方案默认在你的程序中的已分配的缓冲中取样(sample)大约1%的缓存,来检查是否存在泄露。如果存在泄露,你会发现如下日志:

Plain text代码  

  1. 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选项(option)重新启动你的程序,你可以看到在你的程序中最近访问已泄露的内存的位置(location)。下列输出展示了来自单元测试的一个泄露问题(XmlFrameDecoderTest.testDecodeWithXml()):

Java代码  

  1. Running io.netty.handler.codec.xml.XmlFrameDecoderTest  
  2. 15:03:36.886 [main] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected.  
  3. Recent access records: 1  
  4. #1:  
  5.    io.netty.buffer.AdvancedLeakAwareByteBuf.toString(AdvancedLeakAwareByteBuf.java:697)  
  6.    io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithXml(XmlFrameDecoderTest.java:157)  
  7.    io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithTwoMessages(XmlFrameDecoderTest.java:133)  
  8.    ...  
  9.  
  10. Created at:  
  11.    io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:55)  
  12.    io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:155)  
  13.    io.netty.buffer.UnpooledUnsafeDirectByteBuf.copy(UnpooledUnsafeDirectByteBuf.java:465)  
  14.    io.netty.buffer.WrappedByteBuf.copy(WrappedByteBuf.java:697)  
  15.    io.netty.buffer.AdvancedLeakAwareByteBuf.copy(AdvancedLeakAwareByteBuf.java:656)  
  16.    io.netty.handler.codec.xml.XmlFrameDecoder.extractFrame(XmlFrameDecoder.java:198)  
  17.    io.netty.handler.codec.xml.XmlFrameDecoder.decode(XmlFrameDecoder.java:174)  
  18.    io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:227)  
  19.    io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:140)  
  20.    io.netty.channel.ChannelHandlerInvokerUtil.invokeChannelReadNow(ChannelHandlerInvokerUtil.java:74)  
  21.    io.netty.channel.embedded.EmbeddedEventLoop.invokeChannelRead(EmbeddedEventLoop.java:142)  
  22.    io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:317)  
  23.    io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)  
  24.    io.netty.channel.embedded.EmbeddedChannel.writeInbound(EmbeddedChannel.java:176)  
  25.    io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithXml(XmlFrameDecoderTest.java:147)  
  26.    io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithTwoMessages(XmlFrameDecoderTest.java:133)  
  27.    ...  

 

如果你使用Netty 5或以上的版本,还提供了一个额外的信息,帮助我们找到最后操作了(handle)泄露缓冲的handler。下面的例子展示了名为EchoServerHandler#0的handler操作了已泄露的缓冲,并且缓冲已被GC了,这意味着EchoServerHandler#0忘记释放了这个buffer:

 

Java代码  

  1. 12:05:24.374 [nioEventLoop-1-1] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected.  
  2. Recent access records: 2  
  3. #2:  
  4.    Hint: 'EchoServerHandler#0' will handle the message from this point.  
  5.    io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:329)  
  6.    io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)  
  7.    io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:133)  
  8.    io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)  
  9.    io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)  
  10.    io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)  
  11.    io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)  
  12.    java.lang.Thread.run(Thread.java:744)  
  13. #1:  
  14.    io.netty.buffer.AdvancedLeakAwareByteBuf.writeBytes(AdvancedLeakAwareByteBuf.java:589)  
  15.    io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:208)  
  16.    io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:125)  
  17.    io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)  
  18.    io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)  
  19.    io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)  
  20.    io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)  
  21.    java.lang.Thread.run(Thread.java:744)  
  22. Created at:  
  23.    io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:55)  
  24.    io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:155)  
  25.    io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:146)  
  26.    io.netty.buffer.AbstractByteBufAllocator.ioBuffer(AbstractByteBufAllocator.java:107)  
  27.    io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:123)  
  28.    io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)  
  29.    io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)  
  30.    io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)  
  31.    io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)  
  32.    java.lang.Thread.run(Thread.java:744)  

 

泄露检测级别

 

当前有4个泄露检测级别:

  • 禁用(DISABLED)   - 完全禁止泄露检测。不推荐。
  • 简单(SIMPLE)       - 告诉我们取样的1%的缓冲是否发生了泄露。默认。
  • 高级(ADVANCED) - 告诉我们取样的1%的缓冲发生泄露的地方
  • 偏执(PARANOID)  - 跟高级选项类似,但此选项检测所有缓冲,而不仅仅是取样的那1%。此选项在自动测试阶段很有用。如果构建(build)输出包含了LEAK,可认为构建失败。

你可以使用JVM的-Dio.netty.leakDetectionLevel选项来指定泄漏检测级别。

Bash代码  

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

 

避免泄露的最佳实践

 

  • 在简单级别和偏执级别上运行你的单元测试和集成测试(integration tests)。
  • 在rolling out到整个集群之前,使用简单级别,以一个合理的、足够长的时间canary(金丝雀?不明所以。。)你的程序,来发现是否存在泄露。
  • 如果存在泄露,再用高级级别来canary以获得一些关于泄露的提示。
  • 不要部署存在泄露的程序到整个集群。

在单元测试中修复泄露问题

 

在单元测试中很容易忘记释放缓冲。这会产生一个泄露的警告,但并不是说就肯定存在泄露。你可以使用ReferenceCountUtil.releaseLater()工具方法,放弃用try-finally来包裹你的单元测试代码以释放所有的缓冲:

Java代码  

  1. importstatic io.netty.util.ReferenceCountUtil.*;  
  2.  
  3. @Test  
  4. publicvoid testSomething() throws Exception {  
  5.    // ReferenceCountUtil.releaseLater() will keep the reference of buf,  
  6.    // and then release it when the test thread is terminated.  
  7.    ByteBuf buf = releaseLater(Unpooled.directBuffer(512));  
  8.    ...  
  9. }  
目录
相关文章
|
Java
05RPC - netty发送对象
05RPC - netty发送对象
39 0
|
7月前
|
编解码 前端开发 网络协议
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读
171 0
|
Java
Netty之了解 Recycler 对象回收池
Netty之了解 Recycler 对象回收池
199 0
Netty之了解 Recycler 对象回收池
|
Java 应用服务中间件 程序员
netty系列之:使用Jboss Marshalling来序列化java对象
在JAVA程序中经常会用到序列化的场景,除了JDK自身提供的Serializable之外,还有一些第三方的产品可以实现对JAVA对象的序列化。其中比较有名的就是Google protobuf。当然,也有其他的比较出名的序列化工具,比如Kryo和JBoss Marshalling。
|
XML 编解码 JSON
netty案例,netty4.1中级拓展篇三《Netty传输Java对象》
Netty在实际应用级开发中,有时候某些特定场景下会需要使用Java对象类型进行传输,但是如果使用Java本身序列化进行传输,那么对性能的损耗比较大。为此我们需要借助protostuff-core的工具包将对象以二进制形式传输并做编码解码处理。与直接使用protobuf二进制传输方式不同,这里不需要定义proto文件,而是需要实现对象类型编码解码器,用以传输自定义Java对象。
269 0
netty案例,netty4.1中级拓展篇三《Netty传输Java对象》
|
存储 算法 Java
netty系列之:netty中常用的对象编码解码器
我们在程序中除了使用常用的字符串进行数据传递之外,使用最多的还是JAVA对象。在JDK中,对象如果需要在网络中传输,必须实现Serializable接口,表示这个对象是可以被序列化的。这样就可以调用JDK自身的对象对象方法,进行对象的读写。 那么在netty中进行对象的传递可不可以直接使用JDK的对象序列化方法呢?如果不能的话,又应该怎么处理呢? 今天带大家来看看netty中提供的对象编码器。
|
Java 程序员 数据安全/隐私保护
netty系列之:JVM中的Reference count原来netty中也有
netty系列之:JVM中的Reference count原来netty中也有
|
网络协议 Java 前端开发
netty tcp 字节有序->对象有序
io.netty.handler.codec.serialization.ObjectDecoder 将ByteBuf[]反序列化为java对象。 A decoder which deserializes the received ByteBufs into Java objects.  io.netty.handler.codec.serialization.ObjectEncod
1468 0
|
7月前
|
数据可视化 安全 API
Qt 6.1 中的模块变更(从官网文档翻译)
Qt 6.1 中的模块变更(从官网文档翻译)
67 0
|
7月前
|
传感器 API Android开发
Qt 6.2 中的模块变更(从官网文档翻译)
Qt 6.2 中的模块变更(从官网文档翻译)
133 0