一 背景
我用c写的一个小程序发现内存泄露,内存泄露的原因比较明显,程序主要的功能实现对另外一个程序生成的tcp或udp流的处理,以插件的方式实现离线告警,mac告警等功能。
二 内存泄露排查
2.1 简单的top排查
想用简单的排查,先用top观察,用top -p pid观察内存占用情况:
![](https://img.haomeiwen.com/i1737506/e07824c60c828191.png)
由于这个程序是采用插件模式开发,所以就一个个插件卸载,然后观察内存变化,最后在主框架内成功查到一个内存泄露点,解决了大的内存泄露问题,内存泄露点在:
if (NULL == flow_json) return NULL;
cJSON * json = cJSON_Parse(flow_json);
cJSON * create = cJSON_GetObjectItem(json, "CREATE_TIME");
cJSON * src_ip = cJSON_GetObjectItem(json, "SRC_IP");
cJSON * dst_ip = cJSON_GetObjectItem(json, "DST_IP");
cJSON * proto = cJSON_GetObjectItem(json, "PROTOCOL");
cJSON * port = cJSON_GetObjectItem(json, "DST_PORT");
if (create == NULL || src_ip == NULL
|| dst_ip == NULL || proto == NULL
|| port == NULL) {
//在异常返回的时候,没有销毁json对象,导致!!!!
cJSON_Delete(json);
return NULL;
}
选项说明如下:
VIRT — 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
RES — 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
SHR — 共享内存大小,单位kb。比如一些so共享库。
%CPU — 上次更新到现在的CPU时间占用百分比
%MEM — 进程使用的物理内存百分比,手工计算了下,大概和RES内存大小差不多。
TIME+ — 进程使用的CPU时间总计,单位1/100秒
2.2 pidstat工具检查
解决了一个问题,是否还有其他内存泄露,继续从更细微处看内存是否存在泄露:
安装pidstat工具:
yum install sysstat
![](https://img.haomeiwen.com/i1737506/d0530ff1552bdb1d.png)
1)第一次批量处理文件,内存有稍微的增长,初期增长是正常的,因为涉及到新数据的部分需要保存到内存中;
2)再次将这批文件处理状态,更改为未处理状态,执行过程中内存占用变化如下:
![](https://img.haomeiwen.com/i1737506/82cdfa9e0ffec15e.png)
从上图分析内存在增加后,降了下来,说明是正常的,处理完一批文件后,内存没有泄露。
三 程序停不掉问题排查
为了解决内存泄露,特意对程序进行了修改,通过文件来控制是否退出,如果有.exit文件,则程序自动退出,这样就可以方便地使用valgrind进行问题排查了。
在程序停止的时候发现一直停不了。本程序是多线程程序,可以通过多种办法进行排查:
- gdb排查
#在线调试程序
gdb -p pid
#查看线程数量
(gdb)info threads
#切换到xx线程
(gdb)thread xx
#线程堆栈查看
(gdb)where
排查如下:
![](https://img.haomeiwen.com/i1737506/6910888e58fc6108.png)
- pstack排查
1. 通过pstree命令查看这个进程现在的线程情况。
2. 用pstack tid命令排查线程情况。
如下:
![](https://img.haomeiwen.com/i1737506/25d8eb3eb8a4ff43.png)
通过这两个简单的办法,查到了是清理日志的线程休眠的时间太长,导致线程无法停掉。
解决办法就比较简单,由于不是核心业务线程,直接kill杀掉这个线程即可。
四 valgrind 检测内存泄露
程序是定期跑的程序,对于这种程序不方便用valgrind检测,需要进行改造,上面说通过判断是否有文件方式来进行程序异步停止控制,代码实现很简单:
struct stat statbuf;
//检测退出文件是否存在,存在则删除文件,且设置退出标识
if (!lstat("./.exit", &statbuf)) {
remove(".exit");
g_run_flag = QUIT;
}
内存泄露valgrind的执行命令:
valgrind --leak-check=full --show-leak-kinds=all ./flow
说明其中flow为本次出现bug的程序,通过了几次更改了些小的bug,基本把内存泄露解决。
检测如下:
![](https://img.haomeiwen.com/i1737506/a6dcf8d2ce1d66bb.png)
重点先看:
LEAK SUMMARY:
==8920== definitely lost: 0 bytes in 0 blocks
==8920== indirectly lost: 0 bytes in 0 blocks
==8920== possibly lost: 0 bytes in 0 blocks
==8920== still reachable: 520 bytes in 9 blocks
==8920== suppressed: 0 bytes in 0 blocks
definitely lost: 确定的内存泄露,需要立刻修复。申请的内存,没有释放,但是程序中的指针变量无法访问到这块内存,则是确定泄露。
indirectly lost: 间接的内存泄露,一般是结构体内部的成员或类里面成员的指针资源没释放,前面一种情况修复后,这种一般自动会消失。
possibly lost: 可能的内存泄露,一般也是需要修复的,谨防部分申请内存后,释放部分内存的情况。
still reachable: 可以访问,未丢失但是也未释放。一般程序程序结束时候,有些变量没有显示释放,如果是配置数据,可以忽略,因为这部分占的内存是不会增加的,随着程序停止,仍然会被OS回收。
还有一些其他错误,通过其他选项检测下,设置选项如下:
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes -v ./flow
检测到的信息如下:
![](https://img.haomeiwen.com/i1737506/571bba6a2b6cebdb.png)
看下代码:
![](https://img.haomeiwen.com/i1737506/cf2986230fbd036b.png)
通过提示来看tm变量可能未初始化造成的,初始化下:
struct tm tm = { 0 };
![](https://img.haomeiwen.com/i1737506/8bea0e53e5e25503.png)
五 总结
主要内存泄露的地方多是异常情况判断的时候,没有释放掉内存。所以编写代码的时候,申请内存和释放内存一起写,再配合valgrind等工具检测各种条件下的内存是否泄露。
网友评论