直接内存是IO框架的绝配,但直接内存的分配销毁不易,所以使用内存池能大幅提高性能。

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,还在自己地界里的话,一定要进行释放。

多层的异常处理机制,有些异常处理的地方不一定准确知道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这个素数来取模,命中了就创建一个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,平时也不会真用了你的。然后监控其使用量,及时报警。

Netty之内存泄露的更多相关文章

  1. Netty堆外内存泄露排查与总结

    导读 Netty 是一个异步事件驱动的网络通信层框架,用于快速开发高可用高性能的服务端网络框架与客户端程序,它极大地简化了 TCP 和 UDP 套接字服务器等网络编程. Netty 底层基于 JDK ...

  2. Netty如何监控内存泄露

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

  3. 从一次netty 内存泄露问题来看netty对POST请求的解析

    背景 最近生产环境一个基于 netty 的网关服务频繁 full gc 观察内存占用,并把时间维度拉的比较长,可以看到可用内存有明显的下降趋势 出现这种情况,按往常的经验,多半是内存泄露了 问题定位 ...

  4. 抓到 Netty 一个隐藏很深的内存泄露 Bug | 详解 Recycler 对象池的精妙设计与实现

    欢迎关注公众号:bin的技术小屋,如果大家在看文章的时候发现图片加载不了,可以到公众号查看原文 本系列Netty源码解析文章基于 4.1.56.Final版本 最近在 Review Netty 代码的 ...

  5. 《从HBase offheap到Netty的内存管理》

      JVM中的堆外内存(off-heap memory)与堆内内存(on-heap memory) 1. 堆内内存(on-heap memory) 1.1 什么是堆内内存 Java 虚拟机在执行Jav ...

  6. java: web应用中不经意的内存泄露

    前面有一篇讲解如何在spring mvc web应用中一启动就执行某些逻辑,今天无意发现如果使用不当,很容易引起内存泄露,测试代码如下: 1.定义一个类App package com.cnblogs. ...

  7. 查看w3wp进程占用的内存及.NET内存泄露,死锁分析

    一 基础知识 在分析之前,先上一张图: 从上面可以看到,这个w3wp进程占用了376M内存,启动了54个线程. 在使用windbg查看之前,看到的进程含有 *32 字样,意思是在64位机器上已32位方 ...

  8. C++11 shared_ptr 智能指针 的使用,避免内存泄露

    多线程程序经常会遇到在某个线程A创建了一个对象,这个对象需要在线程B使用, 在没有shared_ptr时,因为线程A,B结束时间不确定,即在A或B线程先释放这个对象都有可能造成另一个线程崩溃, 所以为 ...

  9. 基于HTML5的WebGL应用内存泄露分析

    上篇(http://www.hightopo.com/blog/194.html)我们通过定制了CPU和内存展示界面,体验了HT for Web通过定义矢量实现图形绘制与业务数据的代码解耦及绑定联动, ...

随机推荐

  1. hihocoder:#1082 : 然而沼跃鱼早就看穿了一切(用string)

    题目是这样的: 描述 fjxmlhx每天都在被沼跃鱼刷屏,因此他急切的找到了你希望你写一个程序屏蔽所有句子中的沼跃鱼("marshtomp",不区分大小写).为了使句子不缺少成分, ...

  2. UVA - 1631 Locker(密码锁)(dp---记忆化搜索)

    题意:有一个n(n<=1000)位密码锁,每位都是0~9,可以循环旋转.每次可以让1~3个相邻数字同时往上或者往下转一格.输入初始状态和终止状态(长度不超过1000),问最少要转几次. 分析: ...

  3. 配置gitlab代码提交之后自动触发jenkins

    https://www.cnblogs.com/bugsbunny/p/7919993.html

  4. Python时间问题

    获取当前的时间,time只能精确到秒,而datetime可以精确到毫秒,所以使用格式化的时候要注意. nowTime=time.localtime((time.time())) t=time.strf ...

  5. 算法应用杂谈-xgboost的偏差

    一个小样本的cvr 估计问题中, 考虑用xgboost 模型. 发现结果的估计偏差很大. 仔细研究后, 发现现象: 迭代步数不多, 一般3,5步就停了. 预测的分数偏差很大, 分布不匀. pcoc很大 ...

  6. 运行roscore出现unable to contact my own server无法启动小海龟的部分故障问题解决

    运行roscore后,出现下图这种情况(unable to contact my own server) 原因是找不到http://后面那些,ping不到域名或IP. 参考http://www.ros ...

  7. 2.react 基础 - create-react-app 目录结构 及 组件应用

    1. react-app 脚手架的 目录结构 node_modules -d 存放 第三方下载的 依赖的包 public -d    资源目录 favicon.ico - 左上角的图标 index.h ...

  8. 3. laravel 5.5 多子域名 + dingo + jwt 简单环境搭建

    环境介绍 laravel 5.5.* + php 7.2 + mysql 5.7.27 1. 创建 laravel 项目 (自行 配置一下域名 如果 不会 请参考laravel 的第一篇文章) com ...

  9. softmax推导以及多分类的应用

  10. sublime3激活方法

    激活方法参考这里 $ tail -n4 /etc/hosts # https://blog.csdn.net/DeMeng33/article/details/80536926 127.0.0.1 w ...