內存泄漏的概念及其產生原因和規避手段
Memory leak
內存泄漏是指:程序在動態分配內存后,由于某種原因未能釋放或無法釋放這些內存,導致系統內存的浪費。
產生內存泄露的原因
上述定義表示了一種現象,沒有定義原因。要避免這種現象,就要探究產生現象的原因。
內存泄漏是在程序運行過程中產生的,程序運行依賴的是我們的指令,即程序的源代碼。
不同的編程語言其對內存使用方式的表現不同。有的是通過指針,依賴指針來申請與釋放內存;有的語言有內存管理器,通過對象的引用數是否為 0 來決定是否自動回收該對象內存。
產生內存泄漏的原因,從內存指針的角度來說,是申請內存之后的代碼邏輯失去了該內存的指針而不能釋放內存,或者指針都在但忘記釋放內存。
char *p1 = (char *) malloc(10); // 指針 p1 指向 內存地址 1
char *p2 = (char *) malloc(10); // 指針 p2 指向 內存地址 2
p1 = p2; // p1 與 p2 軍指向內存地址 2,導致當前邏輯失去了之前申請的內存地址 1 的指針
// 由于我們編寫的程序往往運行在主循環中,當前邏輯執行完,程序并不會結束運行。
// 所以在當前邏輯結束前如果沒有釋放內存,就會產生內存泄漏的問題。
從對象引用的角度來說,是當前邏輯失去了先前定義對象的引用。
具體表現是定義了一個對象,然后將它的引用交給變量或成員屬性。在之后又將它的引用傳遞給其他的對象,但其他對象只是接收它并在內部使用,不向外部提供任何訪問它或者取消對它的引用的方式。當該我們的代碼邏輯不再需要這個對象時,將變量或成員屬性置空,但此時其他對象仍持有它的引用,導致該對象所使用的內存無法回收。
Data data = new Data(); // 創建對象,賦值給變量,引用數為 1
DoSth doSth = new DoSth(); // 其他對象
doSth.addData(data); // 傳遞引用,引用數為 2
data = null; // 取消引用,引用數為 1
// 當前邏輯不能訪問 Data 對象,也不再使用它,但是內存無法釋放
如果 DoSth 對象的功能的實現依賴 Data 對象,而將 data 變量置空后,之后的邏輯依然依賴使用 DoSth 通過的功能,即使不能使 DoSth 對象取消其對 Data 對象的引用,也是合理的。
但是,如果將 data 變量置空的意圖是完全不再使用該對象,那 DoSth 對象也不應該使用它,此時不能使 DoSth 對象取消其對 Data 對象的引用就是不合理的。邏輯上完全不被使用的數據依然占據的內存,導致內存管理器無法回收,這就產生了內存泄漏。
造成這個問題的原因是,兩個對象的生命周期不一致,被引用對象的生命周期比引用它的對象的生命周期短。
上述情況在 GUI 程序設計中,使用觀察者模式來設計響應式數據,并通過它使視圖和數據交互時,如果使用不當,就會產生:
視圖對象作為觀察者向數據中傳遞其引用,數據對象在變化時通知視圖更新,看起來很棒。但是當原來的視圖被新的視圖替換,不再被使用時,若數據不移除對視圖的引用,會產生內存泄漏。
所以在應用觀察者模式設計類似響應式數據這種觀察者對象的時候,同時提供注冊觀察者與注銷觀察者的功能。
另外,引入生命周期的概念來保持邏輯上的同步能很好地解決內存泄漏的問題。
避免內存泄漏的辦法
時刻牢記:
- 當還需要使用內存或對象時,始終保持其指針或引用。
- 當內存不再使用時,立即釋放。
- 當對象不再使用時,取消所有依賴它的對象對它的引用。這也就意味著若一個對象要引用其他對象,就同時要有取消引用的功能接口。

浙公網安備 33010602011771號