内存泄漏是什么?
程序中定义了一个局部变量,比如一个整数数组 int data[64] ,就定义了一个可以存储 64 个整数的内存段。由于这是一个局部变量,它会从内存空间的栈中分配内存。栈内存由系统自动分配和管理。一旦程序运行超出了这个局部变量的作用域,栈内存就会被系统自动回收,所以不会产生内存泄漏的问题。
再比如,很多时候,我们事先并不知道数据大小,所以你就要用到标准库函数 malloc() , 在程序中动态分配内存。这时候,系统就会从内存空间的堆中分配内存。
堆内存由应用程序自己来分配和管理。除非程序退出,这些堆内存并不会被系统自动释放,而是需要应用程序明确调用库函数 free() 来释放它们。如果应用程序没有正确释放堆内存,就会造成内存泄漏。
虽然可以通过 OOM (Out of Memory)机制杀死进程,但进程在 OOM 前,可能已经引发了一连串的反应,导致严重的性能问题。
其他需要内存的进程,可能无法分配新的内存;内存不足,又会触发系统的缓存回收以及 SWAP 机制,从而进一步导致 I/O 的性能问题等等。
环境准备
预先安装 sysstat、Docker 以及 bcc 软件包
操作和分析
执行下面的命令来运行案例:
docker run --name=app -itd feisky/app:mem-leak
如果案例应用已经正常启动,你应该可以看到下面这个界面:
docker logs app
2th => 1
3th => 2
4th => 3
5th => 5
6th => 8
7th => 13
8th => 21
9th => 34
10th => 55
11th => 89
12th => 144
查看内存泄漏用vmstat
# 每隔3秒输出一组数据
vmstat 3
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 2193836 131956 1144256 0 0 0 29 3327 5690 3 4 93 0 0
0 0 0 2181976 131956 1144272 0 0 0 39 3517 5877 4 4 92 0 0
0 0 0 2182784 131960 1144312 0 0 0 123 3420 5744 3 4 92 0 0
0 0 0 2182272 131968 1144336 0 0 0 32 3435 5711 4 4 92 0 0
0 0 0 2179008 131980 1145224 0 0 0 37 4141 6340 4 4 91 0 0
1 0 0 2174820 131988 1150616 0 0 0 39 4780 7408 4 4 91 0 0
0 0 0 2172404 131988 1158800 0 0 0 33 6209 8840 4 5 90 0 0
0 0 0 2164524 131988 1163780 0 0 0 29 4929 6780 4 5 92 0 0
0 0 0 2153220 131988 1170864 0 0 0 60 5447 7640 4 5 91 0 0
2 0 0 2136264 132372 1196048 0 0 0 193 5702 9200 9 5 86 0 0
0 0 0 2085772 135392 1243056 0 0 3 34083 11060 22553 25 11 43 21 0
内存的 free 列在不停的变化,并且是下降趋势;而 buffer 和 cache 基本保持不变。未使用内存在逐渐减小,而 buffer 和 cache 基本不变,这说明,系统中使用的内存一直在升高。
是不是内存泄漏呢?或者换句话说,有没有简单方法找出让内存增长的进程,并定位增长内存用在哪儿呢?
memleak 可以跟踪系统或指定进程的内存分配、释放请求,然后定期输出一个未释放内存和相应调用栈的汇总情况(默认 5 秒)
/usr/share/bcc/tools/memleak -a -p $(pidof app)
Top 10 stacks with outstanding allocations:
addr = 7ffa90328860 size = 8192
addr = 7ffa90326850 size = 8192
addr = 7ffa9031e810 size = 8192
addr = 7ffa9031c800 size = 8192
addr = 7ffa90324840 size = 8192
addr = 7ffa9032a870 size = 8192
addr = 7ffa90320820 size = 8192
addr = 7ffa9032c880 size = 8192
addr = 7ffa90322830 size = 8192
73728 bytes in 9 allocations from stack
fibonacci+0x1f [app]
child+0x4f [app]
start_thread+0xdb [libpthread-2.27.so]
案例应用 fibonacci() 函数在不停地分配内存,并且这些分配的地址没有被回收。
看案例应用的源代码
docker exec app cat /app.c
long long *fibonacci(long long *n0, long long *n1)
{
long long *v = (long long *) calloc(1024, sizeof(long long));
*v = *n0 + *n1;
return v;
}
void *child(void *arg)
{
long long n0 = 0;
long long n1 = 1;
long long *v = NULL;
for (int n = 2; n > 0; n++) {
v = fibonacci(&n0, &n1);
n0 = n1;
n1 = *v;
printf("%dth => %lld\n", n, *v);
sleep(1);
}
}
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);
}
}