內存泄漏

概述

在計算機科學中,內存泄漏指因爲疏忽或錯誤形成程序未能釋放已經再也不使用的內存。內存泄漏並不是指內存在物理上的消失,而是應用程序分配某段內存後,因爲設計錯誤,致使在釋放該段內存以前就失去了對該段內存的控制,從而形成了內存的浪費。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;
}

摘自:維基百科

相關文章
相關標籤/搜索