编辑推荐: |
本文来自于csdn,文章主要介绍了常见内存泄漏、以实例论述谈内存泄漏从而说明Linux的重要性以及Cache
和 buffers的区别等相关知识。 |
|
在计算机科学中,内存泄漏(memory leak)指由于疏忽或错误使程序未能释放而造成不能再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。内存泄漏与许多其他问题有着相似的症状,并且通常情况下只能由那些可以获得程序源代码的程序员才可以分析出来。然而,有不少人习惯于把任何不需要的内存使用的增加描述为内存泄漏,严格意义上来说这是不准确的。
一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显式释放的内存。应用程序一般使用malloc,calloc,realloc等函数(C++中使用new操作符)从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。
常见内存泄漏:
1.常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
2.偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
3.一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅一块内存发生泄漏。比如,在一个Singleton类的构造函数中分配内存,在析构函数中却没有释放该内存。而Singleton类只存在一个实例,所以内存泄漏只会发生一次。
4.隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
实例论证一:
如果一个程序存在内存泄漏并且它的内存使用量稳定增长,通常不会有很快的症状。每个物理系统都有一个较大的内存量,如果内存泄漏没有被中止(比如重启造成泄漏的程序)的话,它迟早会造成问题。
以实例论述谈内存泄漏从而说明嵌入式系统(Linux)优化的重要性。
实验环境:
1. 准备验证环境:安装有linux系统的pc机,并安装valgrind调试工具;
2. 编写实例相关程序;
3. 通过valgrind调试实例程序,找出内存泄露,内存越界等错误,并分析错误原因,修改源代码,从而说明嵌入式系统(Linux)优化的重要性。
实验内容:
内存泄露(Memory leak)指的是,在程序中动态申请的内存,在使用完后既没有释放,又无法被程序的其他部分访问。内存泄露是在开发大型程序中最令人头疼的问题,以至于有人说,内存泄露是无法避免的。其实不然,防止内存泄露要从良好的编程习惯做起,另外重要的一点就是要加强单元测试(Unit
Test),而valgrind就是这样一款优秀的工具。
(1):程序发生内存泄露的条件及各种情况:
a: 内存申请忘记释放(malloc,free();new,delete);
b:申请内存大小不正确((char*)malloc(0));
c:free()函数使用不正确(int a[];free(a)释放了栈区参数);
d:一段内存释放多次(1个malloc,多个free());
(2):检测程序是否发生内存泄露:
方法有二:
a:模仿用户长时间使用设备;
b:针对某个具体的测试用例。
实验步骤:
1、编写测试程序:sample.c;
#include"stdio.h"
#include"stdlib.h"
void fun()
{
int *p = (int*)malloc(10*sizeof(int));
p[10] = 0;
}
int main(int argc,char *argv[])
{
fun();
return 0;
} |
2、在终端执行命令:gcc -g -O0 sample.c -o sample生成可执行文件;
3、配置Valgrind环境;
4、在确定已正确配置好Valgrind环境后,执行valgrind ./sample命令;
5、使用valgrind检测到程序的内存问题;
6、根据valgrind的检测修改程序得到:sample2.c;
7、执行命令:gcc -g -O0 sample2.c -o sample2、
valgrind ./sample2 可以看到已经没有任何内存问题。
实验结果:
执行valgrind ./sample命令后:
[xuhao@localhost璋冭瘯Valgrind]$ valgrind ./sample
==4068==Memcheck, a memory error detector
==4068==Copyright (C) 2002-2009, and GNU GPL'd,
by Julian Seward et al.
==4068== UsingValgrind-3.5.0 and LibVEX; rerun
with -h forcopyright info
==4068==Command: ./sample
==4068==
==4068==Invalid write of size 4
==4068== at 0x80483DF: fun (sample.c:7)
==4068== by 0x80483F1: main (sample.c:12)
==4068== Address 0x4038050 is 0 bytes after
a block ofsize 40 alloc'd
==4068== at 0x400677E: malloc(vg_replace_malloc.c:195)
==4068== by 0x80483D5: fun (sample.c:6)
==4068== by 0x80483F1: main (sample.c:12)
==4068==
==4068==
==4068== HEAPSUMMARY:
==4068== in use at exit: 40 bytes in 1 blocks
==4068== total heap usage: 1 allocs, 0 frees,
40bytes allocated
==4068==
==4068== LEAKSUMMARY:
==4068== definitely lost: 40 bytes in 1 blocks
==4068== indirectly lost: 0 bytes in 0 blocks
==4068== possibly lost: 0 bytes in 0 blocks
==4068== still reachable: 0 bytes in 0 blocks
==4068== suppressed: 0 bytes in 0 blocks
==4068== Rerunwith --leak-check=full to see
details of leaked memory
==4068==
==4068== Forcounts of detected and suppressed
errors, rerun with: -v
==4068== ERRORSUMMARY: 1 errors from 1 contexts
(suppressed: 12 from 8) |
(1)、实验前准备:
(2)、生成可执行文件sample:
(3)、在终端执行命令:valgrind./sample,可以看到valgrind的表格信息,通过阅读这些信息,可以发现:
a、这是一个对内存的非法写操作,非法写操作的内存是4 bytes;
b、发生错误时的函数堆栈,以及具体的源代码行号;
c、非法写操作的具体地址空间。
最下面的内容是对发现的内存问题和内存泄露问题的总结。内存泄露的大小(40bytes)也能够被检测出来。
sample.c程序显然有两个问题:
a、是fun函数中动态申请的堆内存没有释放;
b、是对堆内存的访问越界。这两个问题均被valgrind发现。
(4)、这是再根据valgrind信息修改后的程序sample2.c在valgrind中运行结果:
实验工具:
Valgrind是一套Linux下,开放源代码(GPL V2)的仿真调试工具的集合。Valgrind由内核(core)以及基于内核的其他调试工具组成。内核类似于一个框架(framework),它模拟了一个CPU环境,并提供服务给其他工具;而其他工具则类似于插件
(plug-in),利用内核提供的服务完成各种特定的内存调试任务。
Valgrind包括如下一些工具:
1、Memcheck。这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等。这也是本文将重点介绍的部分。
2、Callgrind。它主要用来检查程序中函数调用过程中出现的问题。
3、Cachegrind。它主要用来检查程序中缓存使用出现的问题。
4、Helgrind。它主要用来检查多线程程序中出现的竞争问题。
5、Massif。它主要用来检查程序中堆栈使用中出现的问题。
6、Extension。可以利用core提供的功能,自己编写特定的内存调试工具。
从这次实例验证可以看到valgrind工具很是强大,方便、直接的指出了程序的内存问题,极大帮助了大家对有内存问题程序的修改,学会使用valgrind工具内存问题就不再是问题。
(1)、将某个文件拷贝至/tmp目录,使用free命令查看内存变化,并总结其原因;
(2)、查看buffers和Cache的大小,并截图记录;
(3)、将某个文件从某个普通目录拷贝至另一个目录,使用free命令观察内存变化,反复拷贝,观察内存变化,并总结原因;
(4)、查看buffers和Cache的大小,截图记录,并且和上面切图数据进行比较,总结结果,分析原因;
(4)、掌握/proc/sys/vm/drop_cache目录的作用,以及使用参数1、2、3写入该文件后发生的情况,请描述结果;
(5)、编写一个简单的C语言程序,调用释放buffers和Cache的方法,并描述该调用方法的优势;
(6)、查看释放后的buffers和Cache的大小,截图记录,并且和上面切图数据进行比较,总结结果,分析原因;
(7)、分析总结心得。
实验前准备工作:
在/usr下建立新文件lzy,进入lzy优化2,编写测试程序:test.c:#include"stdio.h"
int main()
{
FILE *pfp = popen("echo 1>/proc/sys/vm/drop_caches","r");
pclose(pfp);
return 0; |
生成可执行文件:gcc test.c -o test,调试并运行test,详细结果请见下图:
(2)、使用free命令查看内存变化,并截图记录实验结果,为了与以后实验结果比较;详细结果请见一下截图:可以看到:每次执行free命令buffers值均处于三位数:184、100、128、248;结果数值都不是特别大,其中248也是在此次截图之后并将截图文件保存在lzy优化2文件中才增大。
(3)、从其他文件夹中拷贝doc文件到lzy优化2,doc文件大小8.53M,然后使用free命令查看内存变化,可以从此次是步骤的截图中可以看到buffers实验数值瞬间增大248-->1948;
(4)、执行./test,然后再执行free命令可以看到buffers数值1948-->276,详细实验结果请见一下截图:
根据实验过程中的截图可以观察到执行free命令后,所显示的种种数据:
total:表示物理内存总量;
used:表示总计分配给缓存(包含buffers与cache )使用的数量,但其中可能部分缓存并未实际使用;
free:未被分配的内存;
shared:共享内存;
buffers:系统分配但未被使用的buffers 数量;
cached:系统分配但未被使用的cache 数量。
Cache 和 buffers的区别:
Cache:高速缓存,是位于CPU与主内存间的一种容量较小但速度很高的存储器。由于CPU的速度远高于主内存,CPU直接从内存中存取数据要等待一定时间周期,Cache中保存着CPU刚用过或循环使用的一部分数据,当CPU再次使用该部分数据时可从Cache中直接调用,这样就减少了CPU的等待时间,提高了系统的效率。Cache又分为一级Cache(L1
Cache)和二级Cache(L2 Cache),L1 Cache集成在CPU内部,L2 Cache早期一般是焊在主板上,现在也都集成在CPU内部,常见的容量有256KB或512KBL2
Cache.
Buffers:缓冲区,一个用于存储速度不同步的设备或优先级不同的设备之间传输数据的区域。通过缓冲区,可以使进程之间的相互等待变少,从而使从速度慢的设备读入数据时,速度快的设备的操作进程不发生间断。
Free中的buffers和cache:(它们都是占用内存):
buffers :作为buffer cache的内存,是块设备的读写缓冲区;
cache:作为page cache的内存, 文件系统的cache;
如果 cache 的值很大,说明cache住的文件数很多。如果频繁访问到的文件都能被cache住,那么磁盘的读IO
必会非常小。
以上结论都可以在实验截图中观察到,数据的变化使实验内容更为直接,更加容易得到实验结果,如果需要更为详细实验数据请看上传实验截图。
守护进程对系统的内存使用的影响:
(1)、由于守护进程一直存活,所以其占用的内存将不会被释放;
(2)、一个进程即使什么事情都不做,它引用了一些动态库,也会占用大量的物理内存;
(3)、由于守护进程的生存周期很长,其哪怕只有一点内存泄漏,也会导致系统的内存耗尽;
进程一旦退出,其所占有的内存将被Linux内核回收,所以对于那些生存周期很短的进程来讲,即使有些内存泄漏,其对系统内存使用影响并不大。
因此应该尽量减少系统中守护进程的数量,对于那些提供服务的守护进程来说,尽量做到在进程需要的时候启动,在不需要的时候退出。
B、如何减少守护进程的数量:
首先讨论为什么会存在守护进程:
原因:(1)、系统需要守护进程来侦听某些事件;
(2)、有些进程提供服务的响应时间达不到要求,开发人员便将其放在设备开机时启动,所以转变成了守护进程。
然后针对上面提到的守护进程存在原因一一提出减少守护进程的方法:
针对原因一:在设计守护进程时,开发人员往往没有区分其中的常驻部分和非常驻部分,在侦听到一个事件后,直接启动相应的业务逻辑,而在这些业务逻辑执行完之后,并不会将本已无用的业务逻辑部分释放,这样就造成了侦听部分与业务逻辑部分混在一起都作为守护进程,而使得守护进程部分逻辑比较复杂,一方面占用了大量的内存,另一方面也增大了内存泄漏的几率。
因为,建议将守护进程明确分为常驻部分和非常驻部分,分别让两个不同的进程来完成,将常驻内存部分的逻辑尽量简化。
更进一步,可以考虑将几个守护进程的侦听事件部分合并到一个守护进程,当负责侦听的守护进程接收到消息后,再去启动相应的业务逻辑。
针对原因二:首先要考虑加快进程的启动速度,从而使服务进程达到按需启动的要求;除了优化加载动态库、使用Prelink等方法外,还可以采用一些进程调度的方法来减少守护进程对于内存的影响。
valgrind:
Valgrind是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具。Valgrind这个名字取自北欧神话中英灵殿的入口。Valgrind的最初作者是Julian
Seward,他于2006年由于在开发Valgrind上的工作获得了第二届Google-O'Reilly开源代码奖。
Valgrind遵守GNU通用公共许可证条款,是一款自由软件。
到3.3.0版本为止,Valgrind支持x86、x86-64以及PowerPC上的Linux。除此之外,还有一些其它非正式支持的类Unix平台(如FreeBSD、NetBSD以及Mac
OS X) |