美文网首页
Linux下内存泄漏排查

Linux下内存泄漏排查

作者: 明翼 | 来源:发表于2021-07-26 11:01 被阅读0次

    作为c的程序员,最常见的就是排查内存泄漏,不过我们一般的内存泄漏是针对特定的程序去排查,相对来说比较容易,但是如果是维护人员,不知道哪个程序有内存泄漏,甚至是应用程序的内存泄漏,还是内核的内存泄漏都不明确,所以一定要有一定的查内存泄漏的章法.

    一 虚拟内存泄露

    一般来说,我们观察系统的内存占用喜欢用top命令,然后输入m,对系统中整体的内存占用情况做个排序,然后在重点观察,内存占用排在前几位的进程,再逐步的分析,

    [root@VM-0-2-centos ~]# top -p 5576
    top - 18:21:46 up 198 days, 20:07,  2 users,  load average: 0.10, 0.04, 0.05
    Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.7 us,  0.3 sy,  0.0 ni, 99.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem :  1882008 total,    78532 free,   116516 used,  1686960 buff/cache
    KiB Swap:        0 total,        0 free,        0 used.  1606660 avail Mem 
    
     PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                 
     5576 root      20   0  184064  11248   1124 S  0.0  0.6  10:34.98 nginx   
    

    虽然top 也可以观察到单独的进程的内存变化,不过一般不太好比较内存变化的规律,
    推荐使用pidstat工具,此工具需要先安装,通过命令:

    yum install sysstat
    

    pidstat 基本说明如下:
    -u:默认的参数,显示各个进程的cpu使用统计
    -r:显示各个进程的内存使用统计
    -d:显示各个进程的IO使用情况
    -p:指定进程号
    -w:显示每个进程的上下文切换情况
    -t:显示选择任务的线程的统计信息外的额外信息
    -T { TASK | CHILD | ALL }

    假如我们观察到如下的内存占用情况:pidstat -r -p pid 5

    [root@VM-0-2-centos ~]# pidstat -r -p 5981 5
    Linux 3.10.0-1127.19.1.el7.x86_64 (VM-0-2-centos)   07/24/2021  _x86_64_    (1 CPU)
    
    06:25:55 PM   UID       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
    06:26:00 PM     0      5981      0.20      0.00    4416    352   0.02  a.out
    06:26:05 PM     0      5981      0.00      0.00    4416    352   0.02  a.out
    06:26:10 PM     0      5981      0.20      0.00    4456    352   0.02  a.out
    06:26:15 PM     0      5981      0.00      0.00    4456    352   0.02  a.out
    06:26:20 PM     0      5981      0.00      0.00    4456    352   0.02  a.out
    06:26:25 PM     0      5981      0.20      0.00    4496    352   0.02  a.out
    06:26:30 PM     0      5981      0.00      0.00    4496    352   0.02  a.out
    06:26:35 PM     0      5981      0.20      0.00    4536    352   0.02  a.out
    06:26:40 PM     0      5981      0.00      0.00    4536    352   0.02  a.out
    06:26:45 PM     0      5981      0.20      0.00    4576    352   0.02  a.out
    06:26:50 PM     0      5981      0.00      0.00    4576    352   0.02  a.out
    06:26:55 PM     0      5981      0.20      0.00    4616    352   0.02  a.out
    
    

    我们注意下,VSZ即虚拟内存的占用每10s增加40,单位为k,即10s增加40k的虚拟内存。
    下面来具体分析下这个程序的内存泄露情况。

    二 分析泄露原因

    我们来分析这个进程的内存分布情况,来分析这泄露的内存有什么特点:

    [root@VM-0-2-centos ~]# pmap -x 5981
    5981:   ./a.out
    Address           Kbytes     RSS   Dirty Mode  Mapping
    0000000000400000       4       4       0 r-x-- a.out
    0000000000600000       4       4       4 r---- a.out
    0000000000601000       4       4       4 rw--- a.out
    00007faab436e000    2720     272     272 rw---   [ anon ]
    00007faab4616000    1804     260       0 r-x-- libc-2.17.so
    00007faab47d9000    2048       0       0 ----- libc-2.17.so
    00007faab49d9000      16      16      16 r---- libc-2.17.so
    00007faab49dd000       8       8       8 rw--- libc-2.17.so
    00007faab49df000      20      12      12 rw---   [ anon ]
    00007faab49e4000     136     108       0 r-x-- ld-2.17.so
    00007faab4a06000    2012     212     212 rw---   [ anon ]
    00007faab4c03000       8       8       8 rw---   [ anon ]
    00007faab4c05000       4       4       4 r---- ld-2.17.so
    00007faab4c06000       4       4       4 rw--- ld-2.17.so
    00007faab4c07000       4       4       4 rw---   [ anon ]
    00007ffe0f3f5000     132      16      16 rw---   [ stack ]
    00007ffe0f47c000       8       4       0 r-x--   [ anon ]
    ffffffffff600000       4       0       0 r-x--   [ anon ]
    ---------------- ------- ------- ------- 
    total kB            8940     940     564
    
    

    其中Address为开始的地址,Kbytes是虚拟内存的大小,RSS为真实内存的大小,Dirty为未同步到磁盘上的脏页,Mode为内存的权限,rw为可写可读,rx为可读和可执行。
    通过几次观察,我们发现:

    00007faab436e000    2720     272     272 rw---   [ anon ]
    

    为泄露部分,此为匿名内存区,也就是没有映射文件,为malloc或mmap分配的内存。
    同样是每次增加40K。

    此时还是只能大概知道内存泄露的位置,我们还先找到具体的代码位置,这个该怎么分析?
    代码的申请,无非是通过malloc和brk这些库函数进行内存调用,我们可以用strace跟踪下。

    [root@VM-0-2-centos ~]# strace -f -t -p 5981 -o trace.strace
    strace: Process 5981 attached
    strace: Process 8519 attached
    strace: Process 8533 attached
    strace: Process 8547 attached
    strace: Process 8557 attached
    strace: Process 8575 attached
    ^Cstrace: Process 5981 detached
    

    我们通过-t选项来显示时间,-f来跟踪子进程。直接用cat命令查看跟踪的文件内容,会发现内容相当多,只要是系统调用都打印了出来,可以通过每次增加40k这个有用的信息搜索下:

    [root@VM-0-2-centos ~]# grep 40960  trace.strace 
    5981  19:01:44 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab403a000
    5981  19:01:55 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab4030000
    5981  19:02:06 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab4026000
    5981  19:02:17 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab401c000
    5981  19:02:28 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab4012000
    

    至此我们找到了具体的泄露代码位置。

    看下这个测试代码:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #define _SCHED_H 
    #define __USE_GNU 
    #include <bits/sched.h>
     
    #define STACK_SIZE 40960
     
    int func(void *arg)
    {
        printf("thread enter.\n");
        sleep(1);
        printf("thread exit.\n");
     
        return 0;
    }
    
    
    int main()
    {
        int thread_pid;
        int status;
        int w;
     
        while (1) {
            void *addr = mmap(NULL, STACK_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0);
            if (addr == NULL) {
                perror("mmap");
                goto error;
            }
            printf("creat new thread...\n");
            thread_pid = clone(&func, addr + STACK_SIZE, CLONE_SIGHAND|CLONE_FS|CLONE_VM|CLONE_FILES, NULL);
            printf("Done! Thread pid: %d\n", thread_pid);
            if (thread_pid != -1) {
                do {
                    w = waitpid(-1, NULL, __WCLONE | __WALL);
                    if (w == -1) {
                        perror("waitpid");
                        goto error;
                    }
                } while (!WIFEXITED(status) && !WIFSIGNALED(status));
            }
            sleep(10);
       }
    
     error:
        return 0;
    }
    
    

    这个测试程序利用mmap申请一块匿名私有的内存,clone为系统函数,pthread_create 和fork底层都是调用它,用来创建进程/线程,将func的地址指针存放在子进程堆栈的某个位置处,该位置就是该封装函数本身返回地址存放的位置,最后一个参数为func的执行参数。clone可以更灵活控制共享,比如可以控制是否共享内存空间,是否共享打开文件,是否共享相同的信号处理函数等。

    我们可以看到,mmap申请内存后,需要通过munmap来释放,这里面没有释放,所以导致了虚拟内存泄露,这里面申请的内存只实际使用了4个字节,即复制了func的指针,其他的内存均没有使用,其实仔细观察会发现还有部分的物理内存泄露,每次4个字节,可以通过pmap -x 查到。

    在 waitpid后面添加:munmap(addr,STACK_SIZE); 即可以实现内存的释放。

    三 valgrind 分析程序内存泄露

    这个是比较常见的方法,一般通过下面命令来查看内存泄露:

    valgrind --tool=memcheck --leak-check=full ./b
    
    [root@VM-0-2-centos test]# valgrind --tool=memcheck --leak-check=full ./b
    ==14374== Memcheck, a memory error detector
    ==14374== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==14374== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
    ==14374== Command: ./b
    ==14374== 
    ==14374== Warning: set address range perms: large range [0x5205040, 0x24605040) (undefined)
    address:0x5205040
    ==14374== Warning: set address range perms: large range [0x5205040, 0x24605040) (defined)
    524288000
    ==14374== 
    ==14374== HEAP SUMMARY:
    ==14374==     in use at exit: 524,288,000 bytes in 1 blocks
    ==14374==   total heap usage: 1 allocs, 0 frees, 524,288,000 bytes allocated
    ==14374== 
    ==14374== 524,288,000 bytes in 1 blocks are possibly lost in loss record 1 of 1
    ==14374==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
    ==14374==    by 0x400675: main (test.c:17)
    ==14374== 
    ==14374== LEAK SUMMARY:
    ==14374==    definitely lost: 0 bytes in 0 blocks
    ==14374==    indirectly lost: 0 bytes in 0 blocks
    ==14374==      possibly lost: 524,288,000 bytes in 1 blocks
    ==14374==    still reachable: 0 bytes in 0 blocks
    ==14374==         suppressed: 0 bytes in 0 blocks
    
    

    明确说明在test.c的17行有可能是内存泄露:==14374== by 0x400675: main (test.c:17)
    其他用法看说明吧。

    四 其他内存泄露分析

    其实上面的内存泄露是我们知道了具体的泄露的进程,然后再做详细分析。那么如果不知道哪里内存泄露了,有什么办法,可以通过分析meminfo文件,来观察泄露的类型。

    [root@VM-0-2-centos test]# cat /proc/meminfo
    MemTotal:        1882008 kB
    MemFree:          752948 kB
    MemAvailable:    1610108 kB
    Buffers:          564900 kB
    Cached:           399584 kB
    SwapCached:            0 kB
    Active:           808140 kB
    Inactive:         220812 kB
    Active(anon):      64548 kB
    Inactive(anon):      488 kB
    Active(file):     743592 kB
    Inactive(file):   220324 kB
    Unevictable:           0 kB
    Mlocked:               0 kB
    SwapTotal:             0 kB
    SwapFree:              0 kB
    ....
    
    图片摘抄自《linux内存技术实战课》

    这里面说明的挺详细的了,如果遇到内存问题,观察这个肯定会发现猫腻的。

    五 诗词欣赏

    蝶恋花.春景
    苏轼
    
    花褪残红青杏小。燕子飞时,绿水人家绕。
    枝上柳绵吹又少。天涯何处无芳草。
    
    墙里秋千墙外道。墙外行人,墙里佳人笑。
    笑渐不闻声渐悄。多情却被无情恼。
    

    相关文章

      网友评论

          本文标题:Linux下内存泄漏排查

          本文链接:https://www.haomeiwen.com/subject/nrdvmltx.html