内存泄漏

概述

在计算机科学中,内存泄漏指因为疏忽或错误形成程序未能释放已经再也不使用的内存。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,致使在释放该段内存以前就失去了对该段内存的控制,从而形成了内存的浪费。ios

内存泄漏一般状况下只能由得到程序源代码的程序员才能分析出来。然而,有很多人习惯于把任何不须要的内存使用的增长描述为内存泄漏,即便严格意义上来讲这是不许确的。程序员

内存泄漏会由于减小可用内存的数量从而下降计算机的性能。最终,在最糟糕的状况下,过多的可用内存被分配掉致使所有或部分设备中止正常工做,或者应用程序崩溃。编程

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

在如下状况,内存泄漏致使较严重的后果:服务器

  • 程序运行后置之不理,而且随着时间的流失消耗愈来愈多的内存(好比服务器上的后台任务,尤为是嵌入式系统中的后台任务,这些任务可能被运行后不少年内都置之不理);
  • 新的内存被频繁地分配,好比当显示电脑游戏或动画视频画面时;
  • 程序可以请求未被释放的内存(好比共享内存),甚至是在程序终止的时候;
  • 泄漏在操做系统内部发生;
  • 泄漏在系统关键驱动中发生;
  • 内存很是有限,好比在嵌入式系统或便携设备中;
  • 当运行于一个终止时内存并不自动释放的操做系统之上,并且一旦丢失只能经过重启来恢复。

程序设计问题

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

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

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

通常来讲,自动内存管理对开发者来说比较方便,由于他们不须要实现释放的动做,或担忧清理内存的顺序,而不用考虑对象是否依然被引用。对开发者来讲,了解一个引用是否有必要保持比了解一个对象是否被引用要简单得多。可是,自动内存管理不能消除全部的内容泄漏。工具

影响

若是一个程序存在内存泄漏而且它的内存使用量稳定增加,一般不会有很快的症状。每一个物理系统都有一个较大的内存量,若是内存泄漏没有被停止(好比重启形成泄漏的程序)的话,它早晚会形成问题。性能

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

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

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

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

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

其余内存消耗

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

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

例子

C

下面是一个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的内存空间不能被删除,由于地址丢失了。这些空间已没法再使用。

#include <iostream>
using namespace std;
int main()
{ 
   int *a = new int(123);
   cout << *a << endl;
   // We should write "delete a;" here
   a = new int(456);
   cout << *a << endl;
   delete a;
   return 0;
}

摘自:维基百科

相关文章
相关标签/搜索