引言
記憶體洩漏(Memory Leak)是指程式中已動态配置設定的堆記憶體由于某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導緻程式運作速度減慢甚至系統崩潰等嚴重後果
記憶體洩漏缺陷具有隐蔽性、積累性的特征,比其他記憶體非法通路錯誤更難檢測;因為記憶體洩漏的産生原因是記憶體塊未被釋放,屬于遺漏型缺陷而不是過錯型缺陷;此外,記憶體洩漏通常不會直接産生可觀察的錯誤症狀,而是逐漸積累,降低系統整體性能,極端的情況下可能使系統崩潰
随着計算機應用需求的日益增加,應用程式的設計與開發也相應的日趨複雜,開發人員在程式實作的過程中處理的變量也大量增加,如何有效進行記憶體配置設定和釋放,防止記憶體洩漏的問題變得越來越突出
例如:伺服器應用軟體,需要長時間的運作,不斷的處理由用戶端發來的請求,如果沒有有效的記憶體管理,每處理一次請求資訊就有一定的記憶體洩漏;這樣不僅影響到伺服器的性能,還可能造成整個系統的崩潰。是以,記憶體管理成為軟體設計開發人員在設計中考慮的主要方面
記憶體洩漏的原因
- 從變量存在的時間生命周期的角度上,把變量分為靜态存儲變量和動态存儲變量兩類;靜态存儲變量是指在程式運作期間配置設定了固定存儲空間的變量。動态存儲變量是指在程式運作期間根據實際需要進行動态地配置設定存儲空間的變量
- 程式中所用的資料分别存放在靜态存儲區和動态存儲區中;靜态存儲區資料在程式的開始就配置設定好記憶體區,在整個程式執行過程中它們所占的存儲單元是固定的,在程式結束時就釋放,是以靜态存儲區資料一般為全局變量
- 動态存儲區資料則是在程式執行過程中根據需要動态配置設定和動态釋放的存儲單元,動态存儲區資料有三類函數形參變量、局部變量和函數調用時的現場保護與傳回位址;由于動态存儲變量可以根據函數調用的需要,動态地配置設定和釋放存儲空間,大大提高了記憶體的使用效率,使得動态存儲變量在程式中被廣泛使用。
開發人員進行程式開發的過程使用動态存儲變量時,不可避免地面對記憶體管理的問題。程式中動态配置設定的存儲空間,在程式執行完畢後需要進行釋放。沒有釋放動态配置設定的存儲空間而造成記憶體洩漏,是使用動态存儲變量的主要問題。一般情況下,開發人員使用系統提供的記憶體管理基本函數,如 malloc 、 recalloc 、 calloc 、 free 等,完成動态存儲變量存儲空間的配置設定和釋放
但是,當開發程式中使用動态存儲變量較多和頻繁使用函數調用時,就會經常發生記憶體管理錯誤
例如:
- 配置設定一個記憶體塊并使用其中未經初始化的内容;
- 釋放一個記憶體塊,但繼續引用其中的内容;
- 子函數中配置設定的記憶體空間在主函數出現異常中斷時、或主函數對子函數傳回的資訊使用結束時,沒有對配置設定的記憶體進行釋放;
- 程式實作過程中配置設定的臨時記憶體在程式結束時,沒有釋放臨時記憶體。記憶體錯誤一般是不可再現的,開發人員不易在程式調試和測試階段發現,即使花費了很多精力和時間,也無法徹底消除
記憶體洩漏通常分為一下四類
1)常發性記憶體洩漏
發生記憶體洩漏的代碼會被多次執行,每行一次執行都會導緻一塊記憶體洩漏
2)偶發性記憶體洩漏
發生記憶體洩漏的代碼隻在某些特定的環境或操作中才會發生,常發性和偶發性是相對的,在特定的環境下,偶發性記憶體洩漏也許就變成了常發性
3)一次性記憶體洩漏
發生記憶體洩漏的代碼隻被執行一次
4)隐式記憶體洩漏
程式在運作過程中不停的配置設定記憶體,但直到結束時才釋放記憶體,嚴格的說,并沒有發生記憶體洩漏,因為程式最終釋放了記憶體,但是在伺服器上一個程式,通常運作時間長,不及時釋放記憶體也可能導緻記憶體耗盡;這類被稱為隐式記憶體洩漏
記憶體配置設定政策
程式運作時的記憶體配置設定政策有三種,分别是靜态配置設定,棧式配置設定,和堆式配置設定,對應的,三種存儲政策使用的記憶體空間主要分别是靜态存儲區(也稱方法區)、棧區和堆區
靜态存儲區(方法區)
主要存放靜态資料、全局 static 資料和常量;這塊記憶體在程式編譯時就已經配置設定好,并且在程式整個運作期間都存在
棧區
當方法被執行時,方法體内的局部變量(其中包括基礎資料類型、對象的引用)都在棧上建立,并在方法執行結束時這些局部變量所持有的記憶體将會自動被釋放;因為棧記憶體配置設定運算内置于處理器的指令集中,效率很高,但是配置設定的記憶體容量有限
堆區
又稱動态記憶體配置設定,通常就是指在程式運作時直接 new 出來的記憶體,也就是對象的執行個體;這部分記憶體在不使用時将會由 垃圾回收器來負責回收
棧與堆的差別
在方法體内定義的(局部變量)一些基本類型的變量和對象的引用變量都是在方法的棧記憶體中配置設定的
當在一段方法塊中定義一個變量時,Java 就會在棧中為該變量配置設定記憶體空間,當超過該變量的作用域後,該變量也就無效了,配置設定給它的記憶體空間也将被釋放掉,該記憶體空間可以被重新使用
堆記憶體用來存放所有由 new 建立的對象(包括該對象其中的所有成員變量)和數組
在堆中配置設定的記憶體,将由垃圾回收器來自動管理;在堆中産生了一個數組或者對象後,還可以在棧中定義一個特殊的變量,這個變量的取值等于數組或者對象在堆記憶體中的首位址,這個特殊的變量就是我們上面說的引用變量;我們可以通過這個引用變量來通路堆中的對象或者數組
舉個例子
publicclassSample{ ints1 = 0;
Sample mSample1 = newSample();
publicvoidmethod(){ ints2 = 1; Sample mSample2 = newSample();
Sample mSample3 = newSample();
}
Sample 類的局部變量 s2 和引用變量 mSample2 都是存在于棧中,但 mSample2 指向的對象是存在于堆上的; mSample3 指向的對象實體存放在堆上,包括這個對象的所有成員變量 s1 和 mSample1,而它自己存在于棧中
結論
- 局部變量的基本資料類型和引用存儲于棧中,引用的對象實體存儲于堆中;因為它們屬于方法中的變量,生命周期随方法而結束
- 成員變量全部存儲與堆中(包括基本資料類型,引用和引用的對象實體)—— 因為它們屬于類,類對象終究是要被 new 出來使用的
寫到這裡你就不難看出現在作為一名程式員,那麼就免不了要和 Linux 産生一定的聯系,是以我也建議大家要學習一下 Linux
學 Linux 最好地方式,就是直接去用!直接将自己的開發環境都改成 linux,一開始很蹩腳,很不适應,這很正常。如果你一直感到很舒服,隻能說明你一直沒有進步。想想我們學了那麼多年英語,絕大多數人還是無法掌握英語,看到英國文檔就直接自動屏蔽。其原因都是:一直在學,但從來沒在用;隻學而不用,沒有半點用
在這裡提供一份 Linux 全套學習手冊:可以私信發送“學習” 即可 免費擷取
好了,以上就是今天要分享的内容,大家覺得有用的話,可以點贊分享一下;如果文章中有什麼問題歡迎大家指正;歡迎在評論區或背景讨論哈~