我们在3.1中说了,内存的分配是进程通过malloc()申请得到的,申请之后,并不会立刻为其分配物理内存,而是在首次访问的时候,通过缺页异常陷入内核中分配内存
那么既然是进程申请得到的,那么就可能在管理过程中,出现各种各样的事故
比如,
1.没有正确的回收,导致泄漏
2.访问的是已经分配内存边界外的地址,导致程序异常退出
那么我们就看一下常见的内存泄露的排查和定位
首先进程的内存区域,分为了只读端,数据段,堆,栈以及文件映射端
我们如果申请了一个整数数组 int data[64],定义了一个可以存储64个整数的内存段,这是一个局部变量,会从内存空间的栈中分配内存
栈空间的内存由系统管理,如果程序的运行超过了这个局部变量的作用域,这个栈内存就会被自动回收,不会产生内存泄露问题
堆空间则是最容易产生内存泄露的地方了,因为如果没有在运行的时候合理使用free()释放的话,就会造成内存泄露
其他的区域,比如只读段,因为是只读的,故不会泄露,数据段,因为比如全局变量和静态变量,因为在定义的时候就确定了大小,也不会产生泄露
最后内存映射端,包括动态链接库和共享内存,其中共享内存由程序动态分配管理,所以如果忘了回收,就会导致和堆内存一样的泄露问题
内存泄露,会导致系统内存的耗尽
虽然系统由OOM机制杀死进程,但是导致的性能问题是不可逆的
那么我们如何发现内存泄露并处理呢?
如果存在一个计算斐波那契数列的方法,我们去定位并处理其
运行其程序,并尝试观察其内存情况,这次利用工具是vmstat
利用vmstat去进行查询
# 每隔3秒输出一组数据
$ vmstat 3 procs ———–memory———- —swap– —–io—- -system– ——cpu—– r b swpd free buff cache si so bi bo in cs us sy id wa st procs ———–memory———- —swap– —–io—- -system– ——cpu—– r b swpd free buff cache si so bi bo in cs us sy id wa st 0 0 0 6601824 97620 1098784 0 0 0 0 62 322 0 0 100 0 0 0 0 0 6601700 97620 1098788 0 0 0 0 57 251 0 0 100 0 0 0 0 0 6601320 97620 1098788 0 0 0 3 52 306 0 0 100 0 0 0 0 0 6601452 97628 1098788 0 0 0 27 63 326 0 0 100 0 0 2 0 0 6601328 97628 1098788 0 0 0 44 52 299 0 0 100 0 0 0 0 0 6601080 97628 1098792 0 0 0 0 56 285 0 0 100 0 0 |
在其中,free一列的数值在不断的变化,buffer和cache基本不变
未使用的内存在逐渐减少,而buffer和cache不变,说明系统使用内存一直升高
这不能说明是一定有内存泄露,也可能有应用程序在稳定申请所需内
这时候我们需要查看是否存在内存一直增长的进程,然后尝试处理
一个良好的内存泄露检测工具,memleak 可以跟踪系统或者进程的内存分配,释放请求,然后统计给出一个未释放内存和相应调用栈的汇总情况
$ /usr/share/bcc/tools/memleak -p $(pidof app) -a
Attaching to pid 12512, Ctrl+C to quit. [03:00:41] Top 10 stacks with outstanding allocations: addr = 7f8f70863220 size = 8192 addr = 7f8f70861210 size = 8192 addr = 7f8f7085b1e0 size = 8192 addr = 7f8f7085f200 size = 8192 addr = 7f8f7085d1f0 size = 8192 40960 bytes in 5 allocations from stack fibonacci+0x1f [app] child+0x4f [app] start_thread+0xdb [libpthread-2.27.so] |
memleak的输出可以看到,案例不断的分配内存,这些分配的地址并没有被回收
其中说明了是fibonacci()函数导致的分配内存没有释放
那么查看代码,果然是创建了函数,但是没有free
long long *fibonacci(long long *n0, long long *n1)
{ //分配1024个长整数空间方便观测内存的变化情况 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函数,但是没有释放返回的内存
只需要在print之后,加上free()函数即可
那么我们总结一下
一般来说,我们会使用标准函数malloc()来申请内存,但是别忘了在使用之后,调用free释放掉
现实往往更加的复杂,因为
往往需要异常捕获的时候去释放
在多线程中异步释放
第三方类库中,可能还需要显式释放
最后就是今天说的memleak工具