简明 Netty 引用计数对象,避坑 ByteBuf Leak 泄露

一张图片简单明了说说清楚 netty 引用计数!

原文地址:http://netty.io/wiki/reference-counted-objects.html

       从 Netty4 开始,某些对象的生命周期由其引用计数来管理,一旦不再使用,Netty 就可以将它们(或其共享资源)返回给对象池(或对象分配器)。垃圾收集和引用队列并没有提供不可达的高效实时保证,Netty 提供了一种可替代的机制----引用计数,代价是略有不便。ByteBuf 是最值得注意的一种,它利用了引用计数来提高分配和回收的性能,本文将解释如何进行引用计数, 何时对引用对象进行销毁, 如何解决缓存泄漏 Leak 等问题?

一、直接上图

在这里插入图片描述

二 、 概要说明

2.1 引用计数的基础

  1. 新建引用计数对象的引用计数为 1
  2. 释放引用计数对象时,它的引用计数减少 1,如果引用计数达到 0,则引用计数对象将被重新分配或者将其返回它来自的对象池
  3. 引用计数也可以通过 retain() 操作来增加
  4. 尝试访问引用计数为 0 的引用计数对象将触发 IllegalReferenceCountException

2.2 谁负责销毁?

一般的经验规则是,最后访问引用计数的对象负责对引用计数对象的销毁。

  • 如果【发送】组件将引用计数对象传递给另一个【接收】组件,则发送组件通常不需要销毁它,而是将其推迟到【接收】组件处理。
  • 如果一个组件使用了引用计数对象,并且知道其他任何内容都无法访问这个对象(不传递给其他组件),则此组件应该负责销毁它。

2.3 引用对象的派生

2.3.1 派生方法会创建派生缓冲区

  • 派生的缓冲区不具有自己的引用计数,共享父缓冲区的引用计数。
  • 父缓冲区在创建派生缓冲区时,引用计数不会增加。因此,如果要将派生缓冲区传递给应用程序的其他组件时,则必须先调用 retain 方法。

ByteBuf 的派生方法包括:

  • ByteBuf.duplicate()
  • ByteBuf.slice()
  • ByteBuf.order(ByteOrder)

2.3.2 派生方法会创建派生缓冲区

不是派生的缓冲区,所分配的 ByteBuf 需要被释放。

ByteBuf 的非派生方法:

  • ByteBuf.copy()
  • ByteBuf.readBytes(int)

2.4 缓冲区泄漏问题解决

如果发生泄漏,你可以找到如下日志信息:

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 -Dio.netty.leakDetection.level 来制定泄漏检测级别。

java -Dio.netty.leakDetectionLevel=advanced …

2.4.1 泄漏检测级别

目前有 4 种泄漏检测:

级别说明
DISABLED禁用泄漏检测。不推荐
SIMPLE告知 1% 的缓冲区是否存在泄漏。默认情况
ADVANCED告知1%的缓冲区泄漏的位置。
PARANOID与 ADVANCED 类似,但是检查所有的缓冲区,此选项在自动测试的阶段很有用。如果构建输出包含了 'LEAK: ',构建失败。

2.4.2 避免泄漏的最佳实践

  • 在 PARANOID 和 SIMPLE 泄漏检测级别运行你的单元测试和集成测试。
  • 使用 SIMPLE 级别运行足够长的时间检测是否发生泄漏
  • 如果有泄漏,再使用 ADVANCED 级别获取关于泄漏的提示
  • 不要部署存在泄漏的程序到集群

2.4.3 在单元测试中修复泄漏

在单元测试中,很容易忘记释放缓冲区或者消息。它将生成泄漏警告,这并不意味着你的程序有泄漏。你可以使用ReferenceCountUtil.releaseLater() 方法,而不是使用 try-catch 块来包装单元测试以释放所有的缓冲区。

ByteBuf buf = releaseLater(Unpooled.directBuffer(512));

2.5 其他

2.5.1 ByteBufHolder接口

ByteBuf 有时会被包含在一个缓冲区中,例如 DatagramPacket、HttpContent 和 WebSocketframe。这些类型继承了一个名为ByteBufHolder 的公共接口。ByteBufHolder 共享它所包含的缓冲区的引用计数,就像派生的缓冲区一样。

2.5.2 ChannelHandler中的引用计数

  • 进站消息
  1. 当一个事件循环将数据读入 ByteBuf 并触发一个 ChannelRead() 事件时,pipline 中的 ChannelHandle 负责释放 buffer。因此, 处理接收到的数据的 handler 应该在channelRead() 中调用 buffer 的 release() 方法
  2. 如果处理器将缓冲区(或者任何引用计数对象)传递给下一个处理程序,则不需要释放它。
  3. 如果你有疑问,或者你想要简化消息的释放,可以使用ReferenceCountUtil.release()。
  • 出站消息
  1. 出站消息是由应用程序创建的,将这些消息写入到 pipline 后释放是 Netty 的责任。
  2. 确保正确释放任何中间对象。(比如编码器中使用的 ByteBuf 等)
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值