近日在使用Netty框架开发程序中出现了内存泄露的问题,百度加调试了一番,做个整理。

直接看解决方法请移步Netty内存泄漏解决ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected

1. ByteBuf分类、回收及使用场景

Netty中按是否使用了池化技术,ByteBuf分为两类,一类是非池化的ByteBuf,包括UnpooledHeapByteBuf、UnpooledDirectByteBuf等等,每次I/O读写都会创建一个新ByteBuf,频繁进行大块内存的分配和回收对性能有一定影响,非池化的ByteBuf可以通过JVM GC自动回收,也推荐手动回收UnpooledDirectByteBuf等使用堆外内存的ByteBuf;另一类是池化的ByteBuf,包括pooledHeapByteBuf、pooledDirectByteBuf等等,其先申请一块大内存池,在内存池中分配空间,对于这种应用级别的内存二次分配,就需要手动对池化的ByteBuf进行释放,否则就有可能出现内存泄露的问题。

ByteBuf 该如何选择: 一般业务数据的内存分配选用Java堆内存UnpooledHeapByteBuf,其实现简单,回收快,不会出现内存管理问题;对于I/O数据的内存分配一般选用池化的直接内存PooledDirectByteBuf,避免Java堆内存到直接内存的拷贝,但使用池化ByteBuf时切记自己分配的内存一定要在用完后手动释放。

Netty的接收和发送ByteBuf采用的DirectBuffers,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(heap buffers)进行socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入socket中。相比堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。

2. ByteBuf的计数器实现

那对于池化的ByteBuf在使用完释放回池中时,如何知道自己被引用多少次,并且在没有其他引用的时候被释放呢?ByteBuf的具备实现类都继承了AbstractReferenceCountedByteBuf类,该类实现了对计数器的操作功能。当某一操作使得ByteBuf的引用增加时,调用retain()函数,使计数器的值原子增加,当某一操作使得ByteBuf的引用减少时,调用release()函数,使计数器的值原子减少,减少到0便会触发回收操作。关于AbstractReferenceCountedByteBuf类的源码分析,请见Netty框架AbstractReferenceCountedByteBuf 源码分析

3. ByteBuf计数器的调用场景

(1)当一个ByteBuf新建时,或从另一个ByteBuf创建出独立个体时(比如copy(),readBytes(int length)),新的ByteBuf在初始化的时候,自己的计数器也会初始化。这种ByteBuf使用结束后,要主动释放

(2)有些ByteBuf从另一个ByteBuf衍生出来时(比如decode(),retainedSlice(index, length)),底层共用同一个buffer,也会调用retain()函数,来使得计数器增加。使用完毕也要主动释放。

(3)有些ByteBuf从另一个ByteBuf衍生出来时(比如duplicate(), slice(), order()),底层与父类ByteBuf共用一个buffer,其没有自己的计数器,共用父类ByteBuf的计数器,计数器也不会增加。因此,如果要把这些衍生ByteBuf传递给其他函数时,必须要主动调用retain()函数,并在本函数释放父类ByteBuf,在下一个函数里释放衍生ByteBuf。如下面代码

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

以我遇到的内存泄漏的场景为例

(1)readBytes(int length)函数可能会调用到如下代码,新生成一个ByteBuf

        @Override
protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) {
if (HAS_UNSAFE) {
return PooledUnsafeDirectByteBuf.newInstance(maxCapacity);
} else {
return PooledDirectByteBuf.newInstance(maxCapacity);
}
}

(2)ByteBuf in = (ByteBuf) super.decode(ctx,inByteBuf)  调用decode函数时,会调用到buffer.retainedSlice(index, length)函数,会返回原ByteBuf的一个片段,同时使原ByteBuf的计数器增加。它们底层共用同一个buffer,修改一个会影响另一个。

    final <U extends AbstractPooledDerivedByteBuf> U init(
AbstractByteBuf unwrapped, ByteBuf wrapped, int readerIndex, int writerIndex, int maxCapacity) {
wrapped.retain(); // Retain up front to ensure the parent is accessible before doing more work.
parent = wrapped;
rootParent = unwrapped;
......
  }

4. 谁来释放ByteBuf

最基本的规则是谁最后访问ByteBuf,谁最后负责释放。需注意的是:

(1)发送组件将ByteBuf传递给接收组件,发送组件一般不负责释放,由接收组件释放;

(2)如果一个组件除了接收处理ByteBUf,而不做其他操作(比如再传给其他组件),这个组件负责释放ByteBuf。

例如

Action Who should release? Who released?
1. main() creates buf bufmain() main() releases buf
2. main() calls a() with buf bufa() a() releases buf
3. a() returns buf merely. bufmain() main() releases buf
4. main() calls b() with buf bufb() b() releases buf
5. b() returns the copy of buf bufb() b() releases buf,main() releases copy
6. main() calls c() with copy copyc() c() releases copy 
7. c() swallows copy copyc() c() releases copy 

5. 在 ChannelHandler负责链中,如何释放

(1)在Inbound messages中

a. 如果ChannelHandler中,只有处理ByteBuf的操作,不会调ctx.fireChannelRead(buf)把ByteBuf传递下去,那就要在这个ChannelHandler中释放ByteBuf。

b. 如果ChannelHandler中,会调ctx.fireChannelRead(buf)把ByteBuf传递给下一个ChannelHandler,那在当前ChannelHandler中不需要释放ByteBuf,由最后一个使用该ByteBuf的ChannelHandler释放。

c. 如果处理的ByteBuf是由decode()等会增加计数器的操作生成的,不再传递时,ByteBuf也要释放。

d. 如果不确定要不要释放,或者简化释放的过程,可以调用ReferenceCountUtil.release(ByteBuf)函数。

e. 也可以把ChannelHandler都承继自SimpleChannelInboundHandler虚类,该类会在channelRead函数中调用ReferenceCountUtil.release(msg)来帮助释放ByteBuf,如下代码所示,channelRead0(ctx, imsg)是一个虚函数,子类实现channelRead0函数用来完成处理逻辑。

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean release = true;
try {
if (acceptInboundMessage(msg)) {
@SuppressWarnings("unchecked")
I imsg = (I) msg;
channelRead0(ctx, imsg);
} else {
release = false;
ctx.fireChannelRead(msg);
}
} finally {
if (autoRelease && release) {
ReferenceCountUtil.release(msg);
}
}
}

(2)在Outbound messages中

Outbound messages中的ByteBuf都是由应用程序产生的,由Netty负责释放。

6. 内存泄露检测

使用引用计数的缺点在于容易产生内存泄露,因为JVM不知道引用计数的存在。当一个对象不可达时,JVM可能会收回该对象,但这个对象的引用计数可能还不是0,这就导致该对象从池里分配的空间不能归还到池里,从而导致内存泄露。

Netty提供了一种内存泄露检测机制,可以通过配置参数不同选择不同的检测级别,参数设置为java -Dio.netty.leakDetection.level=advanced

  • DISABLED :完全禁用内存泄露检测,不推荐
  • SIMPLE :抽样1%的ByteBuf,提示是否有内存泄露
  • ADVANCED :抽样1%的ByteBuf,提示哪里产生了内存泄露
  • PARANOID :对每一个ByteBu进行检测,提示哪里产生了内存泄露

我在测试时,直接提示了ByteBuf内存泄露的位置,如下,找到自己程序代码,看哪里有新生成的ByteBuf对象没有释放,主动释放一下,调用对象的release()函数,或者用工具类帮助释放ReferenceCountUtil.release(msg)。

2020-06-12 17:04:41.242 [nioEventLoopGroup-2-1] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See https://netty.io/wiki/reference-counted-objects.html for more information.
Recent access records:
Created at:
io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:363)
io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:187)
io.netty.buffer.AbstractByteBufAllocator.buffer(AbstractByteBufAllocator.java:123)
io.netty.buffer.AbstractByteBuf.readBytes(AbstractByteBuf.java:872)
com.spring.netty.twg.service.TwgMessageDecoder.formatDecoder(TwgMessageDecoder.java:176)
com.spring.netty.twg.service.TwgMessageDecoder.getMessageBody(TwgMessageDecoder.java:90)
com.spring.netty.twg.service.TwgMessageDecoder.decode(TwgMessageDecoder.java:76)
io.netty.handler.codec.LengthFieldBasedFrameDecoder.decode(LengthFieldBasedFrameDecoder.java:332)
io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:501)
2020-06-12 17:04:45.460 [nioEventLoopGroup-2-1] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See https://netty.io/wiki/reference-counted-objects.html for more information.
Recent access records:
Created at:
io.netty.buffer.SimpleLeakAwareByteBuf.unwrappedDerived(SimpleLeakAwareByteBuf.java:143)
io.netty.buffer.SimpleLeakAwareByteBuf.retainedSlice(SimpleLeakAwareByteBuf.java:57)
io.netty.handler.codec.LengthFieldBasedFrameDecoder.extractFrame(LengthFieldBasedFrameDecoder.java:498)
io.netty.handler.codec.LengthFieldBasedFrameDecoder.decode(LengthFieldBasedFrameDecoder.java:437)
com.spring.netty.twg.service.TwgMessageDecoder.decode(TwgMessageDecoder.java:31)
io.netty.handler.codec.LengthFieldBasedFrameDecoder.decode(LengthFieldBasedFrameDecoder.java:332)

参考资料

https://netty.io/wiki/reference-counted-objects.html#who-destroys-it

https://blog.csdn.net/u013202238/article/details/93383887

Netty中ByteBuf内存泄露及释放解析的更多相关文章

  1. Netty如何监控内存泄露

    目录 Netty如何监控内存泄露 前言 JDK的弱引用和引用队列 Netty的实现思路 代码实现 分配监控对象 追踪和检查泄露 Netty如何监控内存泄露 前言 一般而言,在Netty程序中都会采用池 ...

  2. Netty 中的内存分配浅析-数据容器

    本篇接续前一篇继续讲 Netty 中的内存分配.上一篇 先简单做一下回顾: Netty 为了更高效的管理内存,自己实现了一套内存管理的逻辑,借鉴 jemalloc 的思想实现了一套池化内存管理的思路: ...

  3. Netty 中的内存分配浅析

    Netty 出发点作为一款高性能的 RPC 框架必然涉及到频繁的内存分配销毁操作,如果是在堆上分配内存空间将会触发频繁的GC,JDK 在1.4之后提供的 NIO 也已经提供了直接直接分配堆外内存空间的 ...

  4. 查找并修复Android中的内存泄露—OutOfMemoryError

    [编者按]本文作者为来自南非约翰内斯堡的女程序员 Rebecca Franks,Rebecca 热衷于安卓开发,拥有4年安卓应用开发经验.有点完美主义者,喜爱美食. 本文系国内ITOM管理平台 One ...

  5. 利用Instrument Leak来发现App中的内存泄露

    XCode提供了一组用于检测内存,调试动画,布局等的工具.对于调试一些性能问题,内存问题非常方便.这里我们使用Leak来发现代码中的内存泄露. 在Leak中启动我们的应用开始监控: 注意,在监控的时候 ...

  6. Java中的内存泄露 和 JVM GC(垃圾回收机制)

    一.什么是Java中的内存泄露? 在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点, 首先,这些对象是可达的,即在有向图中,存在通路可以与其相连:其次,这些对象是无用的,即程序以 ...

  7. C语言中的内存分配与释放

    C语言中的内存分配与释放 对C语言一直都是抱着学习的态度,很多都不懂,今天突然被问道C语言的内存分配问题,说了一些自己知道的,但感觉回答的并不完善,所以才有这篇笔记,总结一下C语言中内存分配的主要内容 ...

  8. Netty中ByteBuf的引用计数线程安全的实现原理

    原文链接 Netty中ByteBuf的引用计数线程安全的实现原理 代码仓库地址 ByteBuf 实现了ReferenceCounted 接口,实现了引用计数接口,该接口的retain(int) 方法为 ...

  9. Android内存优化8 内存检测工具2 LeakCanary——直白的展现Android中的内存泄露

    之前碰到的OOM问题,终于很直白的呈现在我的眼前:我尝试了MAT,但是发现不怎么会用.直到今天终于发现了这个新工具: 当我们的App中存在内存泄露时会在通知栏弹出通知: 当点击该通知时,会跳转到具体的 ...

  10. LeakCanary——直白的展现Android中的内存泄露

    之前碰到的OOM问题,终于很直白的呈现在我的眼前:我尝试了MAT,但是发现不怎么会用.直到今天终于发现了这个新工具: 当我们的App中存在内存泄露时会在通知栏弹出通知: 当点击该通知时,会跳转到具体的 ...

随机推荐

  1. Kulla-Conty BRDF补充_重要性采样GGX

    重要性采样的方法有多种,这里我们介绍其中一种,你的实现也可以使用其他方法.我们将通过 GGX 采样来完成 E(µ)的预计算工作.先从理论上讨论 GGX 采样算法,对于给定出射方向 o 的 GGX采样, ...

  2. 墨天轮沙龙 | Proxima 刘方:阿里巴巴大规模向量检索实时服务化引擎 Proxima SE

    导读 随着 AI 技术的广泛应用,以及数据规模的不断增长,向量检索也逐渐成了 AI 技术链路中不可或缺的一环. 在11月16日举办的[墨天轮数据库沙龙-向量数据库专场]邀请到阿里巴巴高级技术专家刘方, ...

  3. 11 Self-Attention相比较 RNN和LSTM的优缺点

    博客配套视频链接: https://space.bilibili.com/383551518?spm_id_from=333.1007.0.0 b 站直接看 配套 github 链接:https:// ...

  4. 征服 Docker 镜像访问限制:KubeSphere v3.4.1 成功部署全攻略

    近期,KubeSphere 社区的讨论中频繁出现关于 Docker 官方镜像仓库访问受限的问题. 本文旨在为您提供一个详细的指南, 展示在 Docker 官方镜像访问受限的情况下,如何通过 KubeK ...

  5. 在 KubeSphere 上快速安装和使用 KDP 云原生数据平台

    作者简介:金津,智领云高级研发经理,华中科技大学计算机系硕士.加入智领云 8 余年,长期从事云原生.容器化编排领域研发工作,主导了智领云自研的 BDOS 应用云平台.云原生大数据平台 KDP 等产品的 ...

  6. 云原生周刊:Terraform 1.8 发布 | 2024.5.6

    开源项目推荐 xlskubectl 用于控制 Kubernetes 集群的电子表格.xlskubectl 将 Google Spreadsheet 与 Kubernetes 集成.你可以通过用于跟踪费 ...

  7. KubeSphere 开源社区 2023 年度回顾与致谢

    2023 年结束了,让我们再一次一起回顾一下 KubeSphere 开源社区在过去一年的变化.更重要的是,本篇文章将会对 2023 年所有参与过 KubeSphere 社区贡献的成员致以最诚挚的感谢, ...

  8. 2024SHCTF--Crypto--Week1&Week2--WP

    2024SHCTF 注:针对2024SHCTF赛事,写下自己的解题思路以及个别赛题赛后复现对于题目而产生的理解. Week1 d_known task: from Crypto.Util.number ...

  9. 最新情报 2024免费开源 WAF

    WAF 是 Web Application Firewall 的缩写,也被称为 Web 应用防火墙.区别于传统防火墙,WAF 工作在应用层,对基于 HTTP/HTTPS 协议的 Web 系统有着更好的 ...

  10. 干货分享:Air700ECQ的硬件设计,第三部分

    ​ 5. 电器特性,可靠性,射频特性 5.1. 绝对最大值 下表所示是模块数字.模拟管脚的电源供电电压电流最大耐受值. 表格 17:绝对最大值 参数 最小 最大 单位 VBAT -0.3 4.7 V ...