Linux内存泄漏

0 什么是内存泄漏?

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

1 常见的造成内存泄漏的原因

1.1 指针重新赋值

下面是一段示例代码:

char * p = (char *)malloc(10);
char * np = (char *)malloc(10);

其中,指针变量p和np分别被分配了10个字节的内存,它们各自的内存如图所示
Linux内存泄漏_第1张图片
如果程序执行如下赋值语句:

p=np;

这时候,指针变量p被np指针重新赋值,其结果是p以前所指向的内存位置变成了孤立的内存,如图所示。它无法释放,因为没有指向该位置的引用,从而导致 10 字节的内存泄漏。
Linux内存泄漏_第2张图片
因此,在对指针赋值前,一定确保内存位置不会变为孤立的。

1.2 错误的内存释放

假设有一个指针变量 p,它指向一个10字节的内存位置。该内存位置的第三个字节又指向某个动态分配的 10 字节的内存位置,如图所示。
Linux内存泄漏_第3张图片
如果程序执行如下语句:

free(p);

很显然,如果通过调用 free 来释放指针 p,则 np 指针也会因此而变得无效。np 以前所指向的内存位置也无法释放,因为已经没有指向该位置的指针。换句话说,np 所指向的内存位置变为孤立的,从而导致内存泄漏。
因此,每当释放结构化的元素,而该元素又包含指向动态分配的内存位置的指针时,应首先遍历子内存位置(如本示例中的 np),并从那里开始释放,然后再遍历回父节点,如下面的代码所示:

free(p->np);
free(p);

1.3 返回值的不正确处理

有时候,某些函数会返回对动态分配的内存的引用,如下面的示例代码所示:

char *f()
{
    return (char *)malloc(10);
}
void f1()
{
    f();
}

很明显,函数 f1 中对 f 函数的调用并未处理该内存位置的返回地址,其结果将导致 f 函数所分配的 10 个字节的块丢失,并导致内存泄漏。

1.4 内存malloc()分配后忘记使用free()进行释放

1.5 使用open()、fopen()后忘记使用close()、fclose()

2 内存泄漏的表现

2.1 应用程序崩溃

因为内存泄漏导致已运行的应用程序得不到所需的内存空间而出现崩溃。

2.2 内存占用量持续增长不减

内存泄漏会导致被泄露的内存在本次系统运行期间不能被使用,因此,随着时间的推移,内存占用量是持续增长的。

2.3 触发OOM,进程被kill

当系统无法为新进程分配内存时,可能会触发OOM,系统选择一些进程,然后kill他们。

3 如何定位内存泄漏

3.1 用户态内存泄漏

  1. 使用top指令查看系统中占用内存量较高的进程
top

Linux内存泄漏_第4张图片
观察以下参数:
VIRT 进程使用的虚拟内存
RES 进程使用的真实内存
SHR 共享内存
%MEM 内存占用率

M	按内存占用率(%MEM)排序

Linux内存泄漏_第5张图片
2. 找出内存占用量较高的进程后,记下该进程的PID,如上图的609。
3. 执行如下指令查看进程内存方面的详细信息。

watch -n 1 cat "/proc/"`ps -ef | grep PID | grep -v grep  | awk 'NR==1 {print $2}'`"/status"


我们主要查看一下几个参数的值:
VmPeak:进程使用的虚拟内存的峰值
VmSize:进程当前使用的虚拟内存的大小
VmLck:已锁住的物理内存的大小(不能交换到磁盘)
VmHWM:进程使用的物理内存的峰值
VmRSS:进程当前使用的物理内存的大小
如果VmSize或VmRSS在一段时间内占用异常,可以考虑该进程是否存在内存泄漏。
4. 借助内存泄漏分析工具进行分析
推荐使用内存泄漏分析工具--valgrind

valgrind --tool=memcheck --leak-check=full xxx		//xxx为该进程的二进制可执行文件的绝对路径

执行完毕后我们要观察的主要是如下图的信息:
Linux内存泄漏_第6张图片
Definitely lost:确认丢失,程序中存在内存泄漏,需要尽快修复。
Indirectly lost:间接丢失,常与definitely lost一起出现,只要修复definitely lost即可。
Possibly lost:可能丢失,大多数情况下应视为definitely lost,需要尽快修复。
Still reachable:可以访问的,这些内存没有丢失、没有释放。建议修复。
Suppressed:已解决的,可以无视此部分。
5. 假如有内存泄漏,则需要找出源码进行修复。
那如何快速定位是哪个位置出现了内存泄漏呢?
根据valgrind的打印信息快速定位。举例如下:
image
这个例子就说明了/root目录下的a.out程序存在4字节的内存泄漏,即test()函数里面的new操作存在泄漏,即new完没有对应的delete。(从下往上执行)
找到泄漏的原因,我们就可以在源码中找到对应的位置进行修复。
6. valgrind也不是万能的,存在如下缺点(包括但不限于):
1. valgrind会占用了更多的内存--会达到检测程序的两倍
2. valgrind不检查静态分配数组的使用情况
3. valgrind检测时新启动了一个进程,不能监测正在运行的进程

3.2 内核态内存泄漏

对于内核态内存泄漏的排查方法和工具,我不了解。如有好的参考资料可以评论区留言,谢谢各位dalao。

本文持续更新

你可能感兴趣的:(Linux内存泄漏)