性能测试分析案例-定位内存泄漏

内存泄漏是什么?

程序中定义了一个局部变量,比如一个整数数组 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);
    }
} 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bala5569

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值