内存泄漏

✍ dations ◷ 2025-05-16 13:37:15 #计算机编程,程式错误

内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

内存泄漏通常情况下只能由获得程序源代码的程序员才能分析出来。

内存泄漏会因为减少可用内存的数量从而降低计算机的性能。最终,在最糟糕的情况下,过多的可用内存被分配掉导致全部或部分设备停止正常工作,或者应用程序崩溃。

内存泄漏带来的后果可能是不严重的,有时甚至能够被常规的手段检测出来。在现代操作系统中,一个应用程序使用的常规内存在程序终止时被释放。这表示一个短暂运行的应用程序中的内存泄漏不会导致严重后果。

在以下情况,内存泄漏导致较严重的后果:

以下的例子无需任何程序设计的知识,但能表明如何导致存储器泄漏及其造成的影响。请注意以下的例子是虚构的!

在此例中的应用程序是一个简单软件的一小部分,用来控制升降机的运作。此部分软件当乘客在升降机内按下一楼层的按钮时运行。

当按下按钮时:

此程序有一处会造成存储器泄漏:如果在升降机所在楼层按下该层的按钮(即上述程序的第4步),程序将触发判断条件而结束运行,但存储器仍一直被占用而没有被释放。这种情况发生得越多,泄漏的存储器也越多。

这个小错误不会造成即时影响。因为人不会经常在升降机所在楼层按下同一层的按钮。而且在通常情况下,升降机应有足够的存储器以应付上百次、上千次类似的情况。不过,升降机最后仍有可能消耗完所有存储器。这可能需要数个月或是数年,所以在简单的测试下这个问题不会被发现。

而这个例子导致的后果会是不那么令人愉快。至少,升降机不会再理会前往其他楼层的要求。更严重的是,如果程序需要存储器去开启升降机门,那可能有人被困升降机内,因为升降机没有足够的存储器去开启升降机门。

存储器泄漏只会在程序运行的时间内持续。例如:关闭升降机的电源时,程序终止运行。当电源再度开启,程序会再次运行而存储器会重置,而这种缓慢的泄漏则会从头开始再次发生。

存储器泄漏是程序设计中一项常见错误,特别是使用没有内置自动垃圾回收的编程语言,如C及C++。一般情况下,存储器泄漏发生是因为不能访问动态分配的存储器。目前有相当数量的调试工具用于检测不能访问的内存,从而可以防止存储器泄漏问题,如IBM Rational Purify(英语:IBM Rational Purify)、BoundsChecker(英语:BoundsChecker)、Valgrind、Insure++(英语:Insure++)及memwatch(英语:memwatch)都是为C/C++程序设计亦较受欢迎的存储器调试工具。垃圾回收则可以应用到任何编程语言,而C/C++也有此类库。

提供自动存储器管理的编程语言如Java、C#、VB.NET以及LISP,都不能避免存储器泄漏。例如,程序会把项目加入至列表,但在完成时没有移除,如同人把对象丢到一堆物品中或放到抽屉内,但后来忘记取走这件物品一样。存储器管理器不能判断项目是否将再被访问,除非程序作出一些指示表明不会再被访问。

虽然存储器管理器可以恢复不能访问的存储器,但它不可以释放可访问的存储器因为仍有可能需要使用。现代的存储器管理器因此为程序设计员提供技术来标示存储器的可用性,以不同级别的“访问性”表示。存储器管理器不会把需要访问可能较高的对象释放。当对象直接和一个强引用相关或者间接和一组强引用相关表示该对象访问性较强。(强引用相对于弱引用,是防止对象被回收的一个引用。)要防止此类存储器泄漏,开发者必须使用对象后清理引用,一般都是在不再需要时将引用设成null,如果有可能,把维持强引用的事件侦听器全部注销。

一般来说,自动存储器管理对开发者来讲比较方便,因为他们不需要实现释放的动作,或担心清理内存的顺序,而不用考虑对象是否依然被引用。对开发者来说,了解一个引用是否有必要保持比了解一个对象是否被引用要简单得多。但是,自动存储器管理不能消除所有的内容泄漏。

如果一个程序存在内存泄漏并且它的内存使用量稳定增长,通常不会有很快的症状。每个物理系统都有一个较大的内存量,如果内存泄漏没有被中止(比如重启造成泄漏的程序)的话,它迟早会造成问题。

大多数的现代计算机操作系统都有存储在RAM芯片中主内存和存储在次级存储设备如硬盘中的虚拟内存,内存分配是动态的——每个进程根据要求获得相应的内存。访问活跃的页面文件被转移到主内存以提高访问速度;反之,访问不活跃的页面文件被转移到次级存储设备。当一个简单的进程消耗大量的内存时,它通常占用越来越多的主内存,使其他程序转到次级存储设备,使系统的运行效率大大降低。甚至在有内存泄漏的程序终止后,其他程序需要相当长的时间才能切换到主内存,恢复原来的运行效率。

当系统所有的内存全部耗完后(包括主内存和虚拟内存,在嵌入式系统中,仅有主内存),所有申请内存的操作将失败。这通常导致程序试图申请内存来终止自己,或造成分段内存访问错误(segmentation fault)。现在有一些专门为修复这种情况而设计的程序,常用的办法是预留一些内存。值得注意的是,第一个遭遇得不到内存问题的程序有时候并不是有内存泄漏的程序。

一些多任务操作系统有特殊的机制来处理内存耗尽得情况,如随机终止一个进程(可能会终止一些正常的进程),或终止耗用内存最大的进程(很有可能是引起内存泄漏的进程)。另一些操作系统则有内存分配限制,这样可以防止任何一个进程耗用完整个系统的内存。这种设计的缺点是有时候某些进程确实需要较大数量的内存时,如一些处理图像,视频和科学计算的进程,操作系统需要重新配置。

如内存泄漏发生在内核,表示操作系统自身发生了问题。那些没有完善的内存管理的计算机,如嵌入式系统,会因为一个长时间的内存泄漏而崩溃。

一些被公众访问的系统,如网络服务器或路由器很容易被黑客攻击,加入一段攻击代码,而产生内存泄漏。

值得注意的是,存储器用量持续增加不一定表明存储器泄漏。一些应用程序会存储越来越多数据到存储器中(如用作缓存。如果缓存太大引起问题,这可能是程序设计上的错误,但并非是存储器泄漏因为数据仍被使用。另一方面,程序有可能申请不合理的大量存储器,因为程序设计者假设存储器总是足够运行特定的工作;例如,图像文件处理器会在开始时阅读图像文件的内容并把之存储至存储器中,有时候由于图像文件太大,消耗的存储器超过了可用的内存导致失败。

另一角度讲,内存泄漏是一种特殊的编程错误,如果没有源代码,根据征兆只能猜测可能有内存泄漏。在这种情况下,使用术语“内存消耗持续增加”可能更确切。

下面是一个C语言的例子,在函数f()中申请了内存却没有释放,导致内存泄漏。当程序不停地重复调用这个有问题的函数f,申请内存函数malloc()最后会在程序没有更多可用存储器可以申请时产生错误(函数输出为NULL)。但是,由于函数malloc()输出的结果没有加以出错处理,因此程序会不停地尝试申请存储器,并且在系统有新的空闲内存时,被该程序占用。注意,malloc()返回NULL的原因不一定是因为前述的没有更多可用存储器可以申请,也可能是逻辑地址空间耗尽,在Linux环境上测试的时候后者更容易发生。

 #include <stdio.h> #include <stdlib.h> void f(void) {     void* s;     s = malloc(50); /* 申请内存空间 */     return;  /* 内在泄漏 - 参见以下资料 */      /*       * s 指向新分配的堆空间。      * 当此函数返回,离开局部变量s的作用域后将无法得知s的值,      * 分配的内存空间不能被释放。      *      * 如要「修复」这个问题,必须想办法释放分配的堆空间,      * 也可以用alloca(3)代替malloc(3)。      * (注意:alloca(3)既不是ANSI函数也不是POSIX函数)      */ } int main(void) {     /* 该函数是一个死循环函数 */     while (true) f(); /* Malloc函数迟早会由于内存泄漏而返回NULL*/     return 0; }

C++

以下例子中,存储了整数123的内存空间不能被删除,因为地址丢失了。这些空间已无法再使用。

相关

  • 氨苄青霉素氨苄青霉素(Ampicillin),又称安比西林、氨苄西林,是一种β-内酰胺类抗生素,可治疗多种细菌感染。适应症包含呼吸道感染、泌尿道感染、脑膜炎、沙门氏菌感染症,以及心内膜炎。本品
  • Moschino梦仙奴(Moschino 意大利语发音:),又译莫斯基诺,是意大利的奢侈品牌,1983年由弗兰科·莫斯基诺创立。该公司在1983年由弗兰科·莫斯基诺(1950-1994)创立。他去世后,助手罗赛拉·嘉蒂妮
  • 柠檬酸钙柠檬酸钙是柠檬酸的钙盐。它是一种常见的食物添加剂(E333(iii)),其常用作钙营养补充剂。钙元素占柠檬酸钙的质量的21%。柠檬酸钙是一种无臭的白色粉末,几乎不溶于水。柠檬酸钙是
  • Unsymmetrical dimethylhydrazine偏二甲肼,或称1,1-二甲基联氨、偏二甲基联胺、偏二甲基肼,分子式(CH3)2NNH2,英文缩写UDMH(Unsymmetrical dimethylhydrazine),无色易燃液体。二甲胺与亚硝酸作用后经还原而得。二
  • 失乐园《失乐园》,日本作家渡边淳一的小说,1995年1996年在《日本经济新闻》上连载,同年拍成电影和电视剧。1997年2月讲谈社出版单行本,出版量迄今已超过三百万册。主人公形象来自1936
  • 黑索金黑索金(英文cyclonite),一种高能炸药,学名环三亚甲基三硝胺(cyclotrimethylenetrinitramine),其通称RDX为的缩写。黑索金又名为旋风炸药,化学式(CH2NNO2)3,白色,密度1.816/cm3。原设
  • 澳洲野犬澳洲野犬(学名:)或丁格犬(英语:dingo)是一群史前已经野化的犬,是狼的次级亚种,可能源自伊朗狼(),尽管叫做澳洲野犬,但其分布并不限于澳大利亚,也不是澳大利亚的原生物种。澳洲野犬生活在
  • 裕隆纳智捷篮球队裕隆纳智捷篮球队,是台湾超级篮球联赛(SBL)的其中一支队伍,由裕隆汽车创办人严庆龄组织创建,是台湾第一支民营篮球队,也是中华职业篮球联盟(CBA)、超级篮球联赛(SBL)创始球队之一。从2
  • 卡尔·戈德马克卡尔·戈德马克(德语:Karl Goldmark,匈牙利语:Goldmark Károly,1830年5月18日-1915年1月2日),犹太血统的匈牙利作曲家,小提琴家。生于一贫困家庭,有兄弟姐妹十余人。早年到维也纳音乐
  • 雪村雪村(1969年4月23日-),本名韩健,本籍吉林辽源,北京大学德语系毕业,是一位中国大陆歌手、作曲人、作词人、电影人。2001年夏天,由于其在互联网上的Flash MV《东北人都是活雷锋》一炮