
文章插图
堆外内存统计字段是
DIRECT_MEMORY_COUNTER,我们可以通过反射拿到这个字段,然后定期check这个值,就可以监控 netty 堆外内存的增长情况 。
文章插图
我们通过反射拿到这个字段,然后每隔一秒打印,我为什么要这样做?
因为,通过我们前面的分析,在爆发大量 OOM 现象之前,没有任何可疑的现象,那么只有两种情况,一种是突然某个瞬间分配了大量的堆外内存导致OOM,一种是堆外内存缓慢增长,到达某个点之后,最后一根稻草将机器压垮 。这段代码加上去之后,打包上线 。
阶段5:到底是缓慢增长还是瞬间飙升?代码上线之后,初始内存为 16384k(16M),这是因为线上我们使用了池化堆外内存,默认一个 chunk 为16M,不必过于纠结 。

文章插图
没过一会,内存就开始缓慢飙升,并且没有释放的迹象,20几分钟之后,内存如下

文章插图
到了这里,猜测可能是前面提到的第二种情况,也就是内存缓慢增长造成的 OOM,由于内存实在增长太慢,于是调整机器负载权重为其他机器的两倍,但是仍然是以几K级别在增长,这天刚好是周五,索性就过他个一个周末再开看 。
过完一个愉快的周末之后,到公司第一时间便是连上跳板机,登录线上机器,开始 tail -f 继续查看日志,输完命令之后,怀着期待的心情重重的敲下了回车键

文章插图
果然不出所料,内存一直在缓慢增长,一个周末的时间,堆外内存已经飙到快一个 G 了,这个时候,我竟然想到了一句成语:只要功夫深,铁杵磨成针!虽然堆外内存几个K几个K的在增长,但是只要一直持续下去,总有把内存打爆的时候(线上堆外内存上限设置的是2G) 。
到了这里,我又开始自问自答了:内存为啥会缓慢增长,伴随着什么而增长?因为我们的应用是面向用户端的websocket,那么,会不会是每一次有用户进来,交互完之后,然后离开,内存都会增长一些,然后不释放呢?带着这个疑问,我开始线下模拟 。
阶段6:线下模拟本地起好服务,把监控堆外内存的单位改为以B为单位(因为本地流量较小,打算一次一个客户端连接),另外,本地也使用非池化内存(内存数字较小,容易看出问题),这样,服务端启动之后,控制台打印信息如下

文章插图
在没有客户端接入的时候,堆外内存一直是0,在意料之中 。接下来,怀着着无比激动的心情,打开浏览器,然后输入网址,开始我们的模拟之旅 。
我们的模拟流程是:新建一个客户端链接->断开链接->再新建一个客户端链接->再断开链接

文章插图
如上图所示,一次 connect 和 disconnect 为一次连接的建立与关闭,上图绿色框框的日志分别是两次连接的生命周期 。我们可以看到,内存每次都是在连接被关闭的的时候暴涨 256B 然后不释放,到了这里,问题进一步缩小,肯定是连接被关闭的时候,触发了框架的一个bug,这个bug在触发之前分配了 256B 的内存,然后bug触发,内存没有释放 。问题缩小之后,接下来开始撸源码捉虫!
阶段7:线下排查接下来,我将本地服务重启,开始完整的线下排查过程 。将目光定位到 netty-socketio 这个框架的 disconnect 事件(客户端websocket连接关闭的时候回调用到这里),基本上可以确定是在 disconnect 事件前后申请的内存没有释放

文章插图
这里,在使用 idea debug的时候,要选择只挂起当前线程,这样我们在单步跟踪的时候,控制台仍然可以看到堆外内存统计线程在打印日志 。
客户端连接上之后然后关闭,断点进入到
onDisconnect回调,我特意在此多停留了一会,发现控制台内存并没有飙升(7B这个内存暂时没有去分析,只需要知道,客户端连接断开之后,我们断点hold住,内存还未开始涨),接下来,神奇的一幕出现了,我将断点放开,让程序跑完
推荐阅读
- 如何延长笔记本电脑的电池寿命,有这一篇就够了
- 用户|B站:用户日活暴增32% 每人每天看95分钟 创历史最高记录
- netty系列之:性能为王!创建多路复用http2服务器
- 分享MySQL记录锁、间隙锁、临键锁小案例演示,你学废了吗
- 高血压一次断根方,1个偏方治疗高血压
- linux服务器垃圾清理记
- windows8笔记本连接wifi受限怎么办,笔记本无法连接无线网络
- 笔记本|DNF:沾了2个职业的光!3级buff称号成胚子价,这些职业太幸福
- 如何最有效地记忆小学英语单词
- 阿里架构师整理的 Netty 学习笔记之:Java NIO 网络编程
