1、内存的分配和回收

  • 栈内存:由系统自动分配和管理。比如,程序中定义的局部变量。一旦程序运行超过了这个局部变量的作用域,栈内存就会被系统自动回收,不会产生内存泄漏
  • 堆内存:由应用程序自己来分配和管理。除非程序退出,这些堆内存不会被系统自动释放,而是需要应用程序明确调用库函数 free() 来释放他们。否则会内存泄漏
  • 只读段:包括程序的代码和常量。由于只读,不会再去分配内存,不会内存泄漏
  • 数据段:包括全局变量和静态变量。在定义时已经确定大小,不会内存泄漏
  • 内存映射段:包括动态链接库和共享内存。其中共享内存由程序动态分配和管理。如果程序忘记回收,会导致跟堆内存类似的内存泄漏问题。

虽然系统有 OOM(Out of Memory)机制杀死进程,但在其之前,引发的一连串反应,导致严重的性能问题。
比如,其他需要内存的进程,可能无法分配新的内存;内存不足,又会触发系统的缓存回收以及 SWAP 机制,从而进一步导致 I/O 的性能问题等等。

2、demo

1、准备

bcc工具
计算斐波那契数列的案例,来看看内存泄漏问题的定位和处理方法
斐波那契数列是一个这样的数列:0、1、1、2、3、5、8…,也就是除了前两个数是 0 和 1,其他数都由前面两数相加得到,用数学公式来表示就是 F(n)=F(n-1)+F(n-2),(n>=2),F(0)=0, F(1)=1。

  1. docker run --name=app -itd feisky/app:mem-leak

2、分析

1、vmstat

image.png
可以看到 free不断下降,而buffer 和cache基本不变。说明系统使用的内存升高。

2、memleak

image.png
可以看到,进程 app 在不停地分配内存。并且找到是 fibonacci()函数分配的内存没有释放。

3、分析源码

  1. docker exec app cat /app.c
  2. ...
  3. long long *fibonacci(long long *n0, long long *n1)
  4. {
  5. //分配1024个长整数空间方便观测内存的变化情况
  6. long long *v = (long long *) calloc(1024, sizeof(long long));
  7. *v = *n0 + *n1;
  8. return v;
  9. }
  10. void *child(void *arg)
  11. {
  12. long long n0 = 0;
  13. long long n1 = 1;
  14. long long *v = NULL;
  15. for (int n = 2; n > 0; n++) {
  16. v = fibonacci(&n0, &n1);
  17. n0 = n1;
  18. n1 = *v;
  19. printf("%dth => %lld\n", n, *v);
  20. sleep(1);
  21. }
  22. }
  23. ...

可以看到, child() 调用了 fibonacci() 函数,但并没有释放 fibonacci() 返回的内存。所以,想要修复泄漏问题,在 child() 中加一个释放函数就可以了,比如:

void *child(void *arg)
{
    ...
    for (int n = 2; n > 0; n++) {
        v = fibonacci(&n0, &n1);
        n0 = n1;
        n1 = *v;
        printf("%dth => %lld\n", n, *v);
        free(v);    // 释放内存
        sleep(1);
    }
}