c/c++程序内存泄漏跟踪总结

描述

最近一段时间,服务器频繁出现内存增长严重,导致服务器性能极具下降,由于服务器代码比较庞大,而且是线上问题,所以处理起来比较棘手,好在我通过一些手段定位到了bug的具体位置,故以记录之。

由于是线上问题,所以像valgrind、gdb、memstack基本不适用,就算你是gdb高手,通过gdb adb attach pid 和gdb dump memory 也是不能很准确定位,而valgrind本身的消耗会影响别人测试或者调试。

c/c++程序的内存泄漏,个人认为有几种情况:

1、malloc、memalagin、realloc、calloc没有free

2、给内存指针重新赋值

3、越界导致的内存指针被修改,这个更难,还得解决越界的问题。

4、函数返回的是动态内存指针,却被忽视,如strdup函数

5、结构体本身是动态内存指针,结构体成员里边有动态指针,只释放了结构体,没有释放成员。

6、第三方动态库存在内存泄漏,这个比较麻烦,因为第三方库没有调试信息。但一般pmap能看一部分信息。

 

办法

我本人定位问题,其实也是比较笨的办法,基本上是克隆一台机器,设置相同的触发条件,然后在后台调试输出,也用gdb和valgrind,效率不高。我们的线上问题是发送端速度快,接收端速度慢,导致中间代理层内存暴增。

一、mtrace

mtrace()函数为内存分配函数安装钩子函数(malloc、realloc、memalign、free)。 这些挂钩函数记录有关内存分配的跟踪信息和重新分配。 跟踪信息可用于发现内存泄漏并尝试释放程序中未分配的内存。

编程接口:

       #include <mcheck.h>

       void mtrace(void);

       void muntrace(void);

代码实现:

#include <mcheck.h>

int main(int argc, char **argv)
{
   setenv("MALLOC_TRACE","output",1);

   mtrace();
}

 运行程序之后,在程序的当前目录下会生成output文件,然后使用命令获取堆栈信息:

# mtrace 程序名 output文件 >msg.txt

通过查看msg.txt文件,就可以找到内存泄漏的地方、大小,如:


Memory not freed:
-----------------
           Address     Size     Caller
0x0000000001ed4760     0x18  at 0x7fface2c1780
0x0000000001ed47b0     0x18  at 0x7fface2c1780
0x0000000001ed59f0     0xb0  at 0x7fface2c1780
0x0000000001ed5ab0     0x18  at 0x7fface2c1780
0x0000000001ed5ad0     0x18  at 0x7fface2c1780
0x0000000001ed5af0     0x18  at 0x7fface2c1780

 

2、封装malloc,输出调试信息

封装代码:

void *my_malloc(int size, int zero,const char *file,int line,const char*func)
{
    char *rv;
  
    rv = (char *)malloc(size);

    if (zero)
    {
        if (rv != 0)
        {
            memset(rv, 0, size);
        }
    }

    printf("malloc %s:%d:%s:(%d):%p\n",file,line,func,size,rv);

    return rv;
}

void my_free(void *ptr,const char *file,int line,const char*func)
{
    if (ptr != 0)
    {
        printf("free %s:%d:%s:%p\n",file,line,func,ptr);
        free(ptr);
    }
}

头文件接口代码

void *my_malloc(int size, int zero,const char *file,int line,const char*func);

void my_free(void *ptr,const char *file,int line,const char*func);

#define  mem_free(ptr) my_free(ptr, __FILE__,__LINE__,__func__)

#define mem_malloc(size, zero) my_malloc((size), zero,__FILE__,__LINE__,__func__)

然后在测试的时候会出现很多调用mem_malloc和mem_free的输出信息,可以重定向到一个日志文件,在程序结束时分析日志文件,主要找malloc的指针有没有对应的free输出。可以针对性的写程序进行分析,虽然分析的过程比较慢,但如果分析出来,就能直接根据文件、函数名、行号定位到具体位置。

3、在malloc和free的函数中写记录

在malloc中把指针值写到缓存中,free中把指针值delete掉。在写指针的时候,需要把文件、行号,函数名写进去。这样在程序结束的时候,把缓存数据写到文件中,通过查看文件一目了然。

 

建议

我个人认为在高性能服务器的开发中,随程序运行动态maloc和free是不可取的,而是要使用内存池。像nginx,redis这种高性能中间件,基本都是使用的内存池。内存池能够很好的避免内存无限制增长,在排查问题的时候也比较好定位。

其实以上写的内容在c++中,构造函数和析构函数也能用。如果是c语言,我建议还是在单元测试的时候使用valgrind和gdb调试。

好的c语言程序,一定是合理利用内存,尤其在并发网络服务器中,每个连接的内存资源分配决定了整体性能,像多线程比多进程的内存利用率高,但出问题会导致整个服务崩溃,那么既要多进程的稳定性,又要多线程的性能,那么使用协程就是好的解决办法。

 

 

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页