linux下怎么定位内存泄漏

本文详细介绍了如何在Linux系统中查看内存使用情况,包括系统和进程级别,以及常用的内存管理工具如free、/proc/buddyinfo、smem、top、asan和valgrind在内存泄漏检测中的应用。同时讨论了内核内存泄漏的定位方法和用户态内存过度使用的后果。
摘要由CSDN通过智能技术生成

正常情况下,软件使用的内存是随着时间在一定范围内波动的。内存泄漏说的是随着时间的推移,进程使用的内存越来越多。一般情况下,内存泄漏是程序不断地申请内存,但是没有释放导致的。

 

1 linux 中内存信息查看

要定位内存泄漏问题,首先要知道怎么查看内存使用信息,包括系统级别的内存使用,进程级别的内存使用情况。本节记录几个查看内存信息的命令。

 

1.1 系统级别

1.1.1 free

free 是我们经常使用的命令,显示信息如下图所示,默认单位是 KB。

71932341519643a7dd9dcf731f89dcd8.png

free 命令显示了两行信息,第一行是机器物理内存的使用情况,第二行是 swap 分区的使用情况。

swap 分区的使用情况,一共 3 个数,比较好理解,total 是总大小,used 是已使用的大小,free 是空闲。

 

内存各项的统计说明如下:

total物理内存总量
used真正在使用的内存,这些内存现在在使用,不能回收
free真正空闲的内存,在 buddy 中管理
shared共享内存
buff/cache文件系统页缓存,dentry, inode 缓存
available可以使用的内存

 

total = used + free + buff/cache

3964428 = 904688 + 1389096 + 1670644

used 是真正在使用,不能回收; free 是真正空闲,被伙伴系统管理着;buff/cache 是介于 used 和 free 之外的中间态。total 等于 3 者之和。

shared 是被多个进程共享的内存,是正在被使用,不能回收的内存。

available 是综合 free 和 buff/cache 的结果,表示当前可以使用的内存,包括全部的 free 以及 buff/cache 中可以回收的内存。

 

释放缓存:

linux 中有一个文件 /proc/sys/vm/drop_caches,向这个文件中写 1 会释放页缓存,写 2 会释放 dentry 和 inode 缓存,写 3 会释放页缓存以及 dentry 和 inode 缓存。

 

1.1.2 free 与 /proc/buddyinfo 的关系

在 linux 相关的书籍中经常看到一句话 "页是内存管理的基本单位"。一开始看到这句话的时候,自己感到疑惑,一个页的大小是 4KB,而我们经常使用的内存,比如一个结构体,大小也就是几十个字节,我们申请一个 1 字节的内存也是可以申请出来的。内存管理的基本单位难道不是字节吗,为什么是页呢。

页是内存管理的基本单位,说的是内存管理中的伙伴系统。系统启动之后,一开始内存都是按页被伙伴系统管理的,在伙伴系统中,内存以页为单位进行管理。buddy 是最底层的内存管理的系统,内核中和用户态使用的内存都要从 buddy 中申请。

 

buddy 中有 11 个链表,第一个链表中的一项是 1 个内存连续的页;第二个链表中的一项是 2 个内存连续的页;第三个链表中的一项是 4 个内存连续的页;以此类推,最后一个链表中的一项是 1024 个内存连续的页。

2f240a4ca1c6445d426eba886e668712.png

 

free 命令中的 free 统计表示还在伙伴系统中的内存

cat /proc/buddinfo 显示的伙伴系统的信息。

43abe4e4e3cdc035ab9313a27fdb57d7.png

buddy 显示的是空闲页的信息,第一列的页数是 2 的 0 次方乘以  464(18 + 466),第二列的页数是 2 的 1 次方乘以 215(8 + 207),以此类推,最后一列的页数是 2 的 10 次方乘以 328(2 + 324 + 1)。

总空闲页数是:

1 * 484,2 * 215,4 * 60,8 * 37,16 * 34,32 * 14,64 * 14,128 * 11,256 * 8,512 * 9,1024 * 328。

总的空闲页数是上边数据的和,为 347274,一个页的大小是 4KB,所以总的空闲内存是 1389096KB,与 free 中显示的  free 是相等的。

 

1.2 进程级别

1.2.1 /proc/pid/status

每个进程都会在 /proc 下创建一个文件夹,文件夹的名字是进程的 pid,在这个文件夹下有进程的所有信息。

status 包括进程的一些状态信息,比如 pid, ppid,进程发生的调度次数,进程的内存使用情况等。

 

如下几项,都是对内存的统计:

630923a344e721ba2af0849595d20cff.png

VmPeak进程所占用最大虚拟内存大小
VmSize进程当前虚拟内存大小
VmLck被锁定的内存大小
VmHWM进程所占用物理内存的峰值
VmRSS进程当前占用物理内存的大小
VmData进程数据段的大小
VmStk进程堆栈段的大小
VmExe进程代码的大小
VmLib进程所使用共享库的大小

其中 VmRSS 是实际占用的内存,在定位内存泄漏问题是,一般需要持续观察 VmRSS,是不是随着时间一直在增长。
 

 

1.2.2 smem

smem 可以查看系统里边所有进程的内存使用情况。

如下图所示,smem 统计项包括 4 个:Swap,USS,PSS, RSS。

 

RSS: 实际使用的物理内存,包括共享内存

PSS: 实际使用的物理内存,共享内存是按比例统计,假如一个共享动态库占用了 500KB 的内存,这个库被 5 个进程共享,那么统计到一个进程中的内存就是 100KB。而在 RSS 统计中是每个进程都统计了 500KB

USS: 进程独占的物理内存,不包括共享内存

由此可知,RSS, PSS, USS 的大小关系:USS <= PSS <= RSS

 

smem 命令可以按某一项进行排序,如图中所示按 pss 进行排序,默认是从小到大排序,-r 是翻过来,从大到小排序。

5fed6b326ab91e63670839171192220a.png

 

1.2.3 top

top 命令非常常用,可以查看 cpu 信息,内存信息,平均负载信息等。

top 命令显示的信息如下,可以看到平均负载,每个 cpu 的使用信息,内存的使用情况,swap 的使用情况。另外还能看到每个进程的 cpu 和内存使用情况。

93d81312c12a727e7f12557767e3d696.png

 

2 应用内存泄漏定位

2.1 asan

编译的时候带上标志 gcc -fsanitize=address memleak.c,这样当内存越界的时候或者内存泄漏的时候就能检查出来。

 

内存泄漏:

示例代码如下,在函数 mem_leadk() 中通过 malloc() 申请了 10 字节的内存,然后没有释放,这样在进程退出的时候就可以检查出来存在内存泄漏。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

void mem_leak() {
  char *p = (char *)malloc(10);
  p[5] = 5;
}

int main() {
  mem_leak();
  sleep(5);
  return 0;
}

 

打印信息如下图所示,打印出了泄漏的内存申请时的栈以及泄漏的内存大小等信息。

de4171ccb76c90e433e1e75d4d47a7a8.png

 

内存越界:

示例代码如下,在函数 array_exceeded() 中定义了一个长度为 10 的数组,在访问的时候时候 a[10] 构造数组越界的情况。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

void array_exceeded() {
  int a[10];
  a[0] = 1;
  a[10] = 2;
}

int main() {
  array_exceeded();
  sleep(5);
  return 0;
}

 

打印信息如下,打印了数组越界位置的调用栈等信息。

2934015adf8d2092f24f32375492f59d.png

 

在工作中还用过另一个 gcc 内置的工具(可见 gcc 的功能还是很强大的),用于统计代码覆盖率的工具,coverage。如下代码,编译的时候带上 -coverage 选项,就会生成 一个 .gcno 文件,运行程序的时候会生成 .gcda 文件。最后使用 gcov .gcda 会生成 .gcov 文件。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

void say_hello() {
  printf("hello\n");
}

int main() {
  say_hello();
  return 0;
}

 

执行 gcov .gcda 生成 .gcov 文件的时候就会打印出来覆盖率信息。

b1064e9ddb2f27b0b360dbfda5221af8.png

 

.gcov 文件中的内容显示了每行代码被执行的次数。

f3a4896cbc4abcc2aa5716e1d8a23ae1.png

 

2.2 valgrind

valgrind 也可以用于检测内存泄漏。

valgrind 和 asan 两种方式对比如下:

(1)valgrind 的优点是应用不用重新编译,直接就能运行,缺点是需要专门安装 valgrind

(2)asan 的优点是 gcc 自带的,不用借助于第三方工具

缺点是需要单独编译,带有 asan 的版本只能作为 debug

 

ubuntu 安装 valgrind:

apt-get install valgrind

 

使用 valgrind:

valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all ./a.out

最重要的一个选项是 --leak-check=full

 

valgrind 打印的信息如下,打印信息中包括泄漏的内存信息,以及申请内存的调用栈。

cd8a77ac8bc0aceb921268f95abfeaaf.png

 

2.3 用户态内存泄漏占用内存太多会被 SIGKILL 杀掉

进程占用内存太多的时候会 oom 被系统杀死,杀死进程的信号是 SIGKILL。

如下是一个狂占内存的代码,可以运行观察一下。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
  while (1) {
    char *p = (char *)malloc(4096);
    if (p) {
      p[0] = 10;
      p[1000] = 10;
    }
  }
  return 0;
}

系统杀死进程的时候会打印一条内核日志,使用 dmesg 可以看到。

e2e14fadbf4bf9d0a8973678e0050fbe.png

 

3 内核内存泄漏

内核中使用的内存绝大多数都是从 slab 中申请的。当内核发生内存泄漏的时候,可以查看 slab 的信息,确定哪个池子的内存一直在增长,确定哪个池子在增长就可以进行下一步的定位工作。

 

/proc/slabinfo 可以查看 slab 内存的使用情况。

也可以使用 slabtop 命令查看 slab 内存的使用情况。

 

对于内核开发者来说,可以直接修改内核代码来进行定位。

而如果修改编译内核代码不方便的话,可以使用 linux tracepoint 中的 kmem 相关的 tracepoint 来进行跟踪定位。

举个例子,假如我们通过查看 slab 信息发现有一个内存池占用的内存一直在增长,内存池中的 buffer 大小是 4096。我们就可以使用如下的命令打开 kmalloc 的 tracepoint,如下命令当 kmalloc 申请的内存是 4096 时打印调用栈。基于调用栈信息,我们可以向下进一步分析。

echo 'stacktrace if bytes_req == 4096' >   /sys/kernel/debug/tracing/events/kmem/kmalloc/trigger

 

  • 25
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: linux定位内存泄漏的方法大概有以下几种: 1.使用valgrind等工具检测程序是否存在内存泄漏; 2.编写程序时,使用malloc和free动态分配内存并在适当位置释放; 3.使用top等命令查看程序占用内存情况; 4.对于长时间运行的程序,可以定时进行一次内存检测。 ### 回答2: 内存泄漏(memory leak)是指程序在使用内存时,没有及时归还已经分配的内存,最终导致程序占用的内存越来越多,造成系统资源浪费的问题。在Linux系统中,我们可以采用如下几种方法定位内存泄漏: 1. 使用内存分析工具:Linux下有很多优秀的内存分析工具,例如Valgrind、Memcheck等。Valgrind在内存问题方面是一个强大的工具,可以帮助我们定位内存泄漏。对于一个内存泄漏的程序,我们可以使用Valgrind先检测其是否存在内存泄漏,然后再利用其他工具进行分析。 2. 利用/usr/bin/time命令进行内存分析: /usr/bin/time命令可以用于分析程序的执行时间和内存消耗情况,可以很方便地检测内存泄漏。例如: /usr/bin/time -f 'memory usage: %M' ./program 其中,%M参数表示内存消耗情况,可以通过观察输出结果判断内存是否泄漏。 3. 使用GDB调试工具:GDB是一款强大的调试工具,可以帮助我们定位内存泄漏。我们可以使用GDB来分析程序,通过打印变量值和函数调用信息来查找内存泄漏的原因。 4. 监测系统资源:Linux系统有一个proc文件系统,通过查看/proc/meminfo可以获取系统内存的使用情况,可以通过监测系统内存的使用情况来判断程序是否存在内存泄漏。 综上所述,针对内存泄漏定位,我们可以选择使用内存分析工具、/usr/bin/time命令、GDB调试工具以及监测系统资源等方法进行定位,从而找出内存泄漏的原因,及时进行解决。 ### 回答3: 内存泄漏是指程序在执行过程中无法释放不用的内存,导致系统内存资源浪费,最终可能会导致系统崩溃。Linux系统下,可以采用以下方法定位内存泄漏: 1. top命令 top命令可以查看系统资源占用情况,包括CPU和内存的使用情况。可以使用top命令观察进程使用内存的情况,查看哪个进程使用了大量的内存。 2. ps命令 ps命令可以查看进程占用的内存和CPU使用情况。使用ps命令可以查找进程ID,然后使用top或其他工具进一步分析进程是否存在内存泄漏。 3. pmap命令 pmap命令可以查看进程使用的物理内存和虚拟内存大小,同时还能显示出每个内存块的权限信息、映射的文件名等。使用pmap命令可以查看进程具体使用的内存情况,更方便地分析内存泄漏。 4. valgrind工具 valgrind是一款强大的内存调试工具,可以检测出内存泄漏等常见问题。使用valgrind工具可以找到造成内存泄漏的具体代码位置,并给出详细的报告,方便开发人员进行问题修复。 5. gdb调试工具 gdb是一个强大的调试工具,在调试程序时可以使用gdb查看内存使用情况,帮助定位内存泄漏问题。可以在程序中加入gdb调试语句,或者使用gdb附加到运行的进程上进行检查。 综上所述,定位Linux系统下的内存泄漏问题,可以借助系统自带的命令行工具和第三方工具,如top、ps、pmap、valgrind和gdb等,结合实际情况进行分析处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值