`
邢邢色色
  • 浏览: 230517 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

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

阅读更多

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

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

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

 

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

 

基本的引用计数

 

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

 

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

 

 

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

 

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

 

 

 悬垂(dangling)引用

 

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

 

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

 

 

增加引用计数

 

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

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

 

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;

 

谁来销毁(destroy)

 

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

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

 

下面是一个简单的例子:

 

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();
}

public void main() {
    ...
    ByteBuf buf = ...;
    // 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()创建了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)的一部分内存。子缓冲没有自己的引用计数,而是共享父缓冲的引用计数。

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;

 

注意父缓冲和它的子缓冲共享同样的引用计数,当创建子缓冲时并不会增加对象的引用计数。因此,如果你要传递(pass)一个子缓冲给你的程序中的其他组件的话,你得先调用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, HttpContent,和WebSocketframe。它们都扩展了一个公共接口,ByteBufHolder。

 

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

 

ChannelHandler中的引用计数

 

Inbound消息(messages)

 

当一个事件循环(event loop)读入了数据,用读入的数据创建了ByteBuf,并用这个ByteBuf触发了一个channelRead()事件时,那么管道(pipeline)中相应的ChannelHandler就负责释放这个buffer。因此,处理接收到的数据的handler应该在它的channelRead()中调用buffer的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)生成的消息(messages)对象,这些对象很可能也是引用计数对象:

// 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(msg)。

 

Outbound消息(messages)

 

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

 

// Simple-pass through
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泄露

 

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

 

 

幸运的是,尽管要找到泄露很困难,但Netty提供了一种方案来帮助发现泄露,此方案默认在你的程序中的已分配的缓冲中取样(sample)大约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()):

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)
    ...

 

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

 

泄露检测级别

 

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

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

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

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

 

避免泄露的最佳实践

 

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

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

 

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

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

@Test
public 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));
    ...
}
1
0
分享到:
评论
4 楼 邢邢色色 2015-08-23  
lv12312 写道
楼主,canary是测试的意思, 金丝雀版本,就是还没有做好发布准备的代码版本,用作测试;出处:金丝雀曾在矿井中被用于早期预警


原来如此,学习了,多谢你的答案:)
3 楼 lv12312 2015-08-21  
楼主,canary是测试的意思, 金丝雀版本,就是还没有做好发布准备的代码版本,用作测试;出处:金丝雀曾在矿井中被用于早期预警
2 楼 邢邢色色 2014-06-26  
hu123456 写道
你好,楼主,很佩服楼主对技术的追求,麻烦问下楼主,netty4高并发时jvm内存不断增大的情况见过没,最近一直在头疼这件事情。。。


最近出了本《Netty权威指南》,国人写的,质量应该很好,可以去看看。。
1 楼 hu123456 2014-02-11  
你好,楼主,很佩服楼主对技术的追求,麻烦问下楼主,netty4高并发时jvm内存不断增大的情况见过没,最近一直在头疼这件事情。。。

相关推荐

    最新netty中文文档chm版

    这个“最新Netty中文文档CHM版”为中国的开发者提供了一个方便的中文学习资源,解决了阅读英文原版文档时的语言障碍,使学习过程更为轻松。 Netty 的核心特性包括: 1. **高性能**: Netty 使用了Java NIO(非阻塞I...

    Netty_中文技术文档

    为了帮助初学者快速上手,Netty官方文档提供了详尽的入门教程,其中不乏简单的示例代码,如抛弃协议(DISCARD protocol)的实现。 抛弃协议是网络编程中最基础也是最简单的协议之一,其核心功能是接收数据而不作...

    netty 官方文档

    ### Netty 官方文档知识点解析 #### 一、Netty 概览 - **Netty** 是由 Mina 的作者基于 Mina 打造的一个全新 Java 网络开发框架,它以其简单、易用及高效的特点而受到广泛好评。 - 当前,Netty 由 JBoss 负责维护...

    netty官网学习手册中文版

    这个“netty官网学习手册中文版”针对的是Netty的3.1版本,虽然现在的Netty已经发展到了5.x版本,但3.1版本的知识仍然具有历史参考价值,特别是对于那些初次接触或需要理解Netty基础概念的开发者来说。 1. **Netty...

    Netty-API-文档中文版

    这个“Netty-API-文档中文版”提供了详细的Netty API 使用指南,帮助开发者更容易理解和应用Netty,避免了语言障碍,使得中文环境下的开发工作更加便捷。 Netty 的核心特性包括: 1. **异步模型**:Netty 采用非...

    reactor-netty-core-1.0.15-API文档-中文版.zip

    包含翻译后的API文档:reactor-netty-core-1.0.15-javadoc-API文档-中文(简体)版.zip; Maven坐标:io.projectreactor.netty:reactor-netty-core:1.0.15; 标签:projectreactor、core、reactor、netty、jar包、java...

    netty的中文文档及demo

    一个netty的中文文档和demo。 。

    Netty入门教程文档

    Netty入门教程文档 Netty是Java的网络编程框架,广泛应用于数据采集服务中,本文将对Netty的基本概念和应用进行详细介绍,并将其与ETL技术结合,讲解如何使用Netty进行数据流转和处理。 1. ETL概述 ETL(Extract...

    netty-all-4.1.17.Final-API文档-中文版.zip

    包含翻译后的API文档:netty-all-4.1.17.Final-javadoc-API文档-中文(简体)版.zip; Maven坐标:io.netty:netty-all:4.1.17.Final; 标签:all、netty、jar包、java、中文文档; 使用方法:解压翻译后的API文档,用...

    reactor-netty-http-1.0.11-API文档-中文版.zip

    赠送jar包:reactor-netty-http-1.0.11.jar; 赠送原API文档:reactor-netty-http-1.0.11-javadoc.jar; 赠送源代码:reactor-netty-...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。

    netty-handler-4.1.68.Final-API文档-中文版.zip

    包含翻译后的API文档:netty-handler-4.1.68.Final-javadoc-API文档-中文(简体)版.zip; Maven坐标:io.netty:netty-handler:4.1.68.Final; 标签:netty、handler、中文文档、jar包、java; 使用方法:解压翻译后的...

    netty-all-4.1.68.Final-API文档-中文版.zip

    包含翻译后的API文档:netty-all-4.1.68.Final-javadoc-API文档-中文(简体)版.zip; Maven坐标:io.netty:netty-all:4.1.68.Final; 标签:netty、all、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用...

    netty 4.1 中文api 帮助文档 + 用户指南

    在本文中,我们将深入探讨 Netty 4.1 的中文API帮助文档和用户指南,以及如何利用这些资源来提升你的网络编程技能。 首先,Netty 4.1 中文API帮助文档是理解 Netty 内部机制的关键工具。它包含了详细的类、接口、...

    netty技术文档,socket技术第七章

    Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于...在阅读《第七章:编解码器Codec》这个文档时,你将会深入了解到如何利用 Netty 进行数据编码和解码,以及如何构建自己的自定义编解码器来适应特定需求。

    netty-socketio api接口文档.7z

    Netty-SocketIO API接口文档是为开发者提供关于如何使用Netty-SocketIO框架进行通信的一个详细指南。Netty是一个高性能、异步事件驱动的网络应用程序框架,它简化了创建网络服务,如TCP和UDP服务器的过程。SocketIO...

    netty-3.10.5.Final-API文档-中文版.zip

    包含翻译后的API文档:netty-3.10.5.Final-javadoc-API文档-中文(简体)版.zip; Maven坐标:io.netty:netty:3.10.5.Final; 标签:netty、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开...

    netty-3.7.0.Final-API文档-中文版.zip

    包含翻译后的API文档:netty-3.7.0.Final-javadoc-API文档-中文(简体)版.zip; Maven坐标:io.netty:netty:3.7.0.Final; 标签:netty、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开...

    netty权威文档

    Netty是什么?使用Netty能够做什么?为什么要从传统的Socket开发切换到NIO进行编程?为什么不直接基于JDK的NIO类库编程而选择Netty?如何全面系统地掌握Netty,进行Netty NIO开发、Netty编解码开发、Netty多协议开发?如何...

    netty 中文文档

    Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。它是Java平台上最流行的网络库之一,广泛应用于分布式系统、微服务架构以及实时大数据传输等领域。 《Netty ...

    netty4.0源码,netty例子,netty api文档

    这个压缩包包含的是Netty 4.0.0.CR3版本的相关资源,包括源码、示例以及API文档,对于学习和理解Netty的工作原理以及如何在实际项目中应用Netty非常有帮助。 首先,让我们来详细探讨一下Netty的核心特性: 1. **...

Global site tag (gtag.js) - Google Analytics