阅读更多

2顶
0踩

企业架构

转载新闻 Netty之有效规避内存泄漏

2015-08-19 16:01 by 副主编 mengyidan1988 评论(4) 有18543人浏览
有过痛苦的经历,特别能写出深刻的文章 —— 凯尔文. 肖

直接内存是IO框架的绝配,但直接内存的分配销毁不易,所以使用内存池能大幅提高性能。但,要重新培养被Java的自动垃圾回收惯坏了的惰性。

Netty有一篇必读的文档 官方文档翻译:引用计数对象 ,在此基础上补充一些自己的理解和细节。

1.为什么要有引用计数器

Netty里四种主力的ByteBuf,
其中UnpooledHeapByteBuf 底下的byte[]能够依赖JVM GC自然回收;而UnpooledDirectByteBuf底下是DirectByteBuffer,如Java堆外内存扫盲贴所述,除了等JVM GC,最好也能主动进行回收;而PooledHeapByteBuf 和 PooledDirectByteBuf,则必须要主动将用完的byte[]/ByteBuffer放回池里,否则内存就要爆掉。所以,Netty ByteBuf需要在JVM的GC机制之外,有自己的引用计数器和回收过程。

一下又回到了C的冰冷时代,自己malloc对象要自己free。 但和C时代又不完全一样,内有引用计数器,外有JVM的GC,情况更为复杂。

2. 引用计数器常识
  • 计数器基于 AtomicIntegerFieldUpdater,为什么不直接用AtomicInteger?因为ByteBuf对象很多,如果都把int包一层AtomicInteger花销较大,而AtomicIntegerFieldUpdater只需要一个全局的静态变量。
  • 所有ByteBuf的引用计数器初始值为1。
  • 调用release(),将计数器减1,等于零时, deallocate()被调用,各种回收。
  • 调用retain(),将计数器加1,即使ByteBuf在别的地方被人release()了,在本Class没喊cut之前,不要把它释放掉。
  • 由duplicate(), slice()和order(ByteOrder)所创建的ByteBuf,与原对象共享底下的buffer,也共享引用计数器,所以它们经常需要调用retain()来显示自己的存在。
  • 当引用计数器为0,底下的buffer已被回收,即使ByteBuf对象还在,对它的各种访问操作都会抛出异常。

3.谁来负责Release

在C时代,我们喜欢让malloc和free成对出现,而在Netty里,因为Handler链的存在,ByteBuf经常要传递到下一个Hanlder去而不复还,所以规则变成了谁是最后使用者,谁负责释放。

另外,更要注意的是各种异常情况,ByteBuf没有成功传递到下一个Hanlder,还在自己地界里的话,一定要进行释放。

3.1 InBound Message
在AbstractNioByteChannel.NioByteUnsafe.read() 处,配置好的ByteBufAllocator创建相应ByteBuf并调用 pipeline.fireChannelRead(byteBuf) 送入Handler链。

根据上面的谁最后谁负责原则,每一个Handler对消息可能有三种处理方式

对原消息不做处理,调用 ctx.fireChannelRead(msg)把原消息往下传,那不用做什么释放。
将原消息转化为新的消息并调用 ctx.fireChannelRead(newMsg)往下传,那必须把原消息release掉。
如果已经不再调用ctx.fireChannelRead(msg)传递任何消息,那更要把原消息release掉。
假设每一个Handler都把消息往下传,Handler并也不知道谁是启动Netty时所设定的Handler链的最后一员,所以Netty会在Handler链的最末补一个TailHandler,如果此时消息仍然是ReferenceCounted类型就会被release掉。
不过如果我们的业务Hanlder不再把消息往下传了,这个TailHandler就派不上用场。
3.2 OutBound Message
要发送的消息通常由应用所创建,并调用 ctx.writeAndFlush(msg) 进入Handler链。在每一个Handler中的处理类似InBound Message,最后消息会来到HeadHandler,再经过一轮复杂的调用,在flush完成后终将被release掉。

3.3 异常发生时的释放
多层的异常处理机制,有些异常处理的地方不一定准确知道ByteBuf之前释放了没有,可以在释放前加上引用计数大于0的判断避免异常;

有时候不清楚ByteBuf被引用了多少次,但又必须在此进行彻底的释放,可以循环调用reelase()直到返回true。

4. 内存泄漏检测

所谓内存泄漏,主要是针对池化的ByteBuf。ByteBuf对象被JVM GC掉之前,没有调用release()去把底下的DirectByteBuffer或byte[]归还到池里,会导致池越来越大。而非池化的ByteBuf,即使像DirectByteBuf那样可能会用到System.gc(),但终归会被release掉的,不会出大事。

Netty担心大家一定会不小心就搞出个大新闻来,因此提供了内存泄漏的监测机制。

Netty默认就会从分配的ByteBuf里抽样出大约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()

这句话报告有泄漏的发生,提示你用-D参数,把防漏等级从默认的simple升到advanced,具体看到被泄漏的ByteBuf创建的地方和被访问的地方。
  • 禁用(DISABLED) - 完全禁止泄露检测,省点消耗。
  • 简单(SIMPLE) - 默认等级,告诉我们取样的1%的ByteBuf是否发生了泄露,但总共一次只打印一次,看不到就没有了。
  • 高级(ADVANCED) - 告诉我们取样的1%的ByteBuf发生泄露的地方。每种类型的泄漏(创建的地方与访问路径一致)只打印一次。
  • 偏执(PARANOID) - 跟高级选项类似,但此选项检测所有ByteBuf,而不仅仅是取样的那1%。在高压力测试时,对性能有明显影响。

实现细节
每当各种ByteBufAllocator 创建ByteBuf时,都会问问是否需要采样,Simple和Advanced级别下,就是以113这个素数来取模(害我看文档的时候还在瞎担心,1%,万一泄漏的地方有所规律,刚好躲过了100这个数字呢,比如都是3倍数的),命中了就创建一个Java堆外内存扫盲贴里说的PhantomReference。然后创建一个Wrapper,包住ByteBuf和Reference。

Simple级别下,wrapper只在执行release()时调用Reference.clear()把Reference清理掉,Advanced级别下则会记录每一个创建和访问的动作。

当GC发生,还没有被clear()的Reference就会被JVM放入到之前设定的ReferenceQueue里。

在每次创建PhantomReference时,都会顺便看看有没有因为忘记执行release()把Reference给clear掉,在GC时被放进了ReferenceQueue的对象,有则以 "io.netty.util.ResourceLeakDetector”为logger name,写出前面例子里的Error级别的日日志。顺便说一句,Netty能自动匹配日志框架,先找Slf4j,再找Log4j,最后找JDK logger。

值得说三遍的事
一定要盯紧log里有没有出现 "LEAK: "字样,因为Simple级别下它只会出现一次,所以不要依赖自己的眼睛,要依赖grep。如果出现了,而且你用的是PooledBuf,那一定是问题,不要有任何的侥幸,立刻用"-Dio.netty.leakDetectionLevel=advanced" 再跑一次,看清楚它创建和最后访问的地方。

功能测试时,最好开着"-Dio.netty.leakDetectionLevel=paranoid"

但是,怎么测试都可能有没覆盖到的分支,如果内存尚够,可以适当把-XX:MaxDirectMemorySize 调大,反正只是max,平时也不会真用了你的。然后监控其使用量,及时报警。

本文转自:花钱的年华
来自: 花钱的年华
2
0
评论 共 4 条 请登录后发表评论
4 楼 有贝无患 2017-10-05 11:32
1楼2楼3楼的想法确实没错,但各位忽略了下面几点:
1、文章中所讲的ByteBuf是池化的ByteBuf;
2.池化的ByteBuf的byte是向内存池申请的、不是向jvm直接申请的,ByteBuf只是内部有个指针指向内存池中的byte数组,所以ByteBuf的byte回收不受jvm直接控制,也就是ByteBuf对象被JVM GC掉,其使用的byte不会被JVM GC掉,除非自身释放掉。
3.内存池是用Java实现的,本质就是个Java对象,该对象在netty使用过程一直存在直到程序结束。所以内存池的内存是自始至终不会被释放,只是谁申请了给谁用,谁归还了再重新分配其他申请者,从而循环使用。如果申请不释放,会导致内存池中的可以分配的byte越来越少,达到某个阈值后会再向jvm申请更多内存。最终结果是内存池中越来越多的内存没有被使用但因没归还而无法再分配,造成内存泄漏。
3 楼 pentiumchen 2015-08-27 22:24
jd2bs 写道
pentiumchen 写道
ByteBuf对象被JVM GC掉之前,没有调用release()去把底下的DirectByteBuffer或byte[]归还到池里,会导致池越来越大

这块有个疑问,为什么会导致池越来越大?ByteBuf从池中分配对象之后,池中的byte数组就分配给memory属性了,如果ByteBuf被GC,那么在没有归还byte的情况下,这部分byte内存应该会随着ByteBuf被回收掉。这种情况下,池应该是越来越小吧?不知道我理解是否有问题


我的理解是 池消耗的内存越来越大,因为借出的byte很大一部分不归还了...但是池本身是不断被借的,它需要不断申请byte...这样就造成了内存泄露


即使byte不归还,byte也会随着借用它的ByteBuf的回收而回收,也不会造成泄露
2 楼 jd2bs 2015-08-25 08:54
pentiumchen 写道
ByteBuf对象被JVM GC掉之前,没有调用release()去把底下的DirectByteBuffer或byte[]归还到池里,会导致池越来越大

这块有个疑问,为什么会导致池越来越大?ByteBuf从池中分配对象之后,池中的byte数组就分配给memory属性了,如果ByteBuf被GC,那么在没有归还byte的情况下,这部分byte内存应该会随着ByteBuf被回收掉。这种情况下,池应该是越来越小吧?不知道我理解是否有问题


我的理解是 池消耗的内存越来越大,因为借出的byte很大一部分不归还了...但是池本身是不断被借的,它需要不断申请byte...这样就造成了内存泄露
1 楼 pentiumchen 2015-08-19 21:21
ByteBuf对象被JVM GC掉之前,没有调用release()去把底下的DirectByteBuffer或byte[]归还到池里,会导致池越来越大

这块有个疑问,为什么会导致池越来越大?ByteBuf从池中分配对象之后,池中的byte数组就分配给memory属性了,如果ByteBuf被GC,那么在没有归还byte的情况下,这部分byte内存应该会随着ByteBuf被回收掉。这种情况下,池应该是越来越小吧?不知道我理解是否有问题

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • Netty 防止内存泄漏措施

    某直播平台,一些网红的直播间在业务高峰期,会有 10W+ 的粉丝接入,如果瞬间发生大量客户端连接掉线、或者一些客户端网络比较慢,发现基于 Netty 构建的服务端内存会飙升,发生内存泄漏(OOM),导致直播卡顿、或者...

  • Netty内存池化(四)规避内存泄漏

    内存检测原理使用的是PhantomReference技术,通过判断ByteBuf的refCount是否为0,判断是否存在内存泄漏。 1.检测原理 无论是池化的ByteBuf还是非池化的ByteBuf,BuyeBuf(不一定是该对象)对象在被gc回收之后,...

  • 记一次SOFA内存泄漏排查过程

    记一次内存泄漏排查过程 起因      某天中午大家还在安静的午休,睡得正香的时候突然被一阵手机滴…滴滴直响短信惊醒。一看是应用的服务器告警并且对应服务的所有机器都在告警“健康检查...

  • Netty内存泄露

    在测试中发现,当不停的开关Netty的NioClientSocketChannelFactory(比如大量连接失败重连等情况下),存在Direct Memory泄露。 测试代码: Java代码 for (int i = 0; i   ChannelFactory ...

  • Netty对JDK缓冲区的内存池零拷贝改造

    NIO中缓冲区是数据传输的基础,JDK通过ByteBuffer实现,Netty框架中并未采用JDK原生的ByteBuffer,而是构造了ByteBuf。 ByteBuf对ByteBuffer做了大量的优化,比如说内存池,零拷贝,引用计数(不依赖GC),本文主要...

  • threadlocal内存泄露_ThreadLocal原理分析

    为什么还有内存泄漏问题,怎么规避? 不妨反过来想想,如果使用强引用,当 ThreadLocal 对象的引用被回收了,ThreadLocalMap( ThreadLocalMap 由 Thread 持有)本身依然还持有 ThreadLocal 的强引用,如果没有手动...

  • netty系列之netty可靠性原理

    2. Netty高可靠性之道 2.1. 网络通信类故障 2.1.1. 客户端连接超时 在传统的同步阻塞编程模式下,客户端Socket发起网络连接,往往需要指定连接超时时间,这样做的目的主要有两个: 1) 在同步阻塞I/O模型中,...

  • Netty权威指南总结(二)

    Netty权威指南总结

  • netty中的bytebuf(脱离JVM的垃圾收集实现手动回收)及引用和内存泄漏

    Netty之有效规避内存泄漏   Heap buffers need to be copied to the direct memory before being handled by the kernel, when you're performing I/O operations. When you use direct buffers you save ...

  • 操作系统课程设计任务书

    《操作系统》 课程设计任务书     指导教师:刘  刚 设计者: 班级: 学号:           山东建筑工程学院计算机系软件教研室     操作系统课程设计题目     一.  混合索引模式下文件空间管理 设计要求: 编写一程序,模拟混合索引形式的文件系统。能接收建立、删除、读、写和查询属性的命令,并在模拟文件系统中进行操作。   例:   create a1.doc 8000    /

  • 操作系统课程设计完成了

    终于弄好了课程设计,不过不是自己做的 二:  进程调度模拟程序 设计要求: 编写一程序,可以创建若干个虚拟进程,并对若干个虚拟进程进行调度,调度策略为时间片轮转。 虚拟程序的描述:    虚拟指令的格式:   操作命令  操作时间 其中,操作命令有以下几种: l        C : 表示在CPU上计算 l        I :表示输入 l        O:表示输出 l        W:表示等

  • 双绞线与RJ-45水晶头的接法

    今天也接了一回 橙白 橙 绿白 蓝 蓝白 绿 棕白 棕,按这个顺序排起来就行啦

  • 找到一个非常不错的文章保存软件

    在网上游荡经常碰到好的文章要保存起来,但是手动保存很不方便而且还不好管理,在网上找了找相关的软件,终于发现一个非常不错的东东--网文快捕 这是介绍: 网文快捕 (原名WebCatcher) 是一个保存/管理网页的工具,主要功能有:可以在IE里面保存网页,包括文字,图片,Flash动画等等。也可以保存选中的文字,图片和链接等等;在一个树形孔建里面管理保存下来的网页。可以通过拖放来分类您的网页;在一个

  • 写了针对学校论坛的刷钱工具

    我校的建院学子论坛是用的CPB论坛,在每次发贴有个境遇问题,来改变发贴人的钱或魅力,看着很有意思,经过几次发贴,突然发现回贴后出现的页面,通过按F5刷新同样可以经过改变境遇来增加钱数或魅力值,于是明白了白小痴怎么会有这么多钱,不过按F5也太慢了,当然也写一个测试程序了,程序其实很简单,就是一个通过socket向指点的HTTP主机发送数据包通过IRIS截获 所发送的数据包为 GET /bbs/pos

  • 留言版

    朋友们可以在这里写下留言

  • 使用vector 模板求素数

    #include #include vector>#include using namespace std;int main(int argc,char *argv[]){        if(argc!=2){        cerr"Usage:/n/t"" "        return 1;    }    int count=0; 

  • 终于得到一个 Wallop邀请

    终于得到一个 Wallop邀请

  • 学校课程设计之我见

             又到了一学期一度的课程设计,好像每个人都很忙,每次都去上机,但是真正做出东西的人却只有几个,最后交的东西也是差不多,老师一看也知道是抄的,但课程设计还是要做,老师也很习惯让每个人过. 我感觉有份心还不如老师在外面找活我们一块干对谁都好

  • 明天考试

    明天就要考试了,今天自然就该努力复习,因为平常没有学习好,但是已经习惯了不复习的我哪里能静下心来,想想大学的学习真是太枯燥了,没有学什么东西,但是一点也不后悔,还是抓紧复习吧.

Global site tag (gtag.js) - Google Analytics