漫談內(nèi)存泄漏

前言

最近看了同事整理的一份與內(nèi)存泄漏相關(guān)思維導圖。突然想從內(nèi)存泄漏的角度探討一下與內(nèi)存相關(guān)的話題。
什么是內(nèi)存泄漏?然而我又問自己一個問題, malloc 的內(nèi)存到底是什么?

什么是內(nèi)存

在計算機系統(tǒng)中,我們談論的內(nèi)存通常是指DRAM 。

虛擬內(nèi)存空間

而當我們程序在系統(tǒng)上運行起來時,操作系統(tǒng)為我們提供了一個假象,程序看起來是獨占使用處理器、主存和I/O設備的。這種假象是通過進程的概念來實現(xiàn)的。

虛擬內(nèi)存,也為進程提供一個假象,每個進程都獨占地使用主存。每個進程看到的內(nèi)存都是一致的,稱為虛擬內(nèi)存空間。

虛擬內(nèi)存的能力:

  • 使主存中只保存活動區(qū)域,根據(jù)需要在磁盤和主存之間來回傳輸數(shù)據(jù)。
  • 為每個內(nèi)存提供一致的地址空間。
  • 保護了每個進程的地址空間不被其他進程破壞。
一個 Linux 進程的虛擬內(nèi)存.png

上圖是一個 Linux 進程的虛擬內(nèi)存。也就是說我們平時 malloc 得到的內(nèi)存地址,其實是虛擬內(nèi)存的地址。

動態(tài)內(nèi)存分配

其實系統(tǒng)為我們提供了 mmapmunmap 函數(shù)來創(chuàng)建和刪除虛擬內(nèi)存的區(qū)域。但很多時候直到程序?qū)嶋H運行才知道某些數(shù)據(jù)結(jié)構(gòu)的大小。所以就有了動態(tài)內(nèi)存分配器。

動態(tài)內(nèi)存分配器有兩種基本類型:

  • 顯式分配器,需要顯式釋放。例如 C 和 C++ 。
  • 隱式分配器,分配器檢測已分配何時不再被程序所使用,那么就釋放這個塊。例如 Lisp、Java 等。

什么是內(nèi)存泄漏

內(nèi)存泄漏是常見的內(nèi)存錯誤之一。我們知道 malloc 其實是從虛擬內(nèi)存空間的堆中申請空閑的地址的。然而內(nèi)存空間是有限的。程序在運行中 malloc 出來的內(nèi)存空間使用完后,沒有被 free 掉,這樣我們就稱之為內(nèi)存泄漏

ARC 機制

iOS 上,不論是 Objective-C 還是 Swift 都是使用引用計數(shù)式的內(nèi)存管理方式。
ARC 就是 Automatic Reference Counting, 其實 ARC 很簡單,我們只需要弄清楚對象之間的持有關(guān)系。

舉個簡單的例子:

場景一

self.textField.text = @"Sim";

下圖簡單的描述了,這段代碼對象之間的持有關(guān)系。 self.textField.text 持有著 @"Sim"。@"Sim" 對象的 retainCount = 1。

1.png
self.textField.text = @"SimCai";

這時,對象 @"Sim" 不再被 self.textField.text 持有,所以 @"Sim" 對象的 retainCount = 0,對象會被釋放掉。

2.png

場景二

self.textField.text = @"Sim";
NSString *firstName = self.textField.text;

這段代碼,@"Sim" 對象被 firstNameself.textField.text 同時持有。所以 @"Sim" 對象的 retainCount = 2 。

3.png
self.textField.text = @"SimCai";

這時 self.textField.text 不再持有 @"Sim" 對象,但是 firstName 依然持有 @"Sim" ,所以 @"Sim" 的 retainCount = 1 ,不會被釋放。

4.png

直到 firstName 也不再持有 @"Sim" 對象,@"Sim" 才會被釋放。

5.png

循環(huán)引用

ARC 整套機制看起來很簡單,但會不會有什么特例,會造成內(nèi)存無法被正常釋放呢?
有,循環(huán)引用

ViewController 持有 TableView,同時 TableView 也持有 ViewController 。 他們相互有引用關(guān)系。這就是循環(huán)引用。

6.png

為了打破這種引用的循環(huán)。我們可以通過 weak (弱引用) 來解決這個問題。

一般情況下,ViewController 是會被個 UINavigationController 所持有。如果 TableView 也持有 ViewController ,這時 ViewController 的 retainCount = 2。

而我們對 self.vc 使用了 weak 后,self.vc = ViewController ,這樣的操作,不再會導致 retainCount 加1 。 這時 ViewController 依然還是 retainCount = 1。

7.png

而當 ViewController 被釋放后 self.vc 建會指向 nil 。當然在這個例子,在 ViewController 釋放后,TableView 自然也會別釋放。

8.png

但在一些場景下使用 weak 需要比較注意的。例如,一個全局的定時器,如果持有了 ViewController 是弱引用。 那當 ViewController 被釋放后,定時器再去訪問 ViewController 就將引起 crash 。

常見的內(nèi)存泄漏場景

  • 使用時 block (需要格外小心)
  • NSTimer 沒有銷毀
  • KVO 沒有移除
  • NSNotification 沒有移除

當了解了 ARC 后,再我看來這些本質(zhì)都是循環(huán)引用問題(當然還有一些 CF 的API,還是需要手動內(nèi)存管理的)。

  • block 會捕獲變量
  • NSTimer 需要持有對象,進行通知回調(diào)
  • KVO 需要持有對象,進行通知回調(diào)
  • NSNotification 需要持有對象,進行通知回調(diào)

所以這些操作都容易造成內(nèi)存泄漏。

要避免內(nèi)存泄漏,更重要的是需要了解 ARC 的機制。實際上,可能造成內(nèi)存泄漏的場景還有很多。

總結(jié)

  • malloc 得到的內(nèi)存地址,實際是虛擬內(nèi)存空間中的堆地址。并不是實際的物理地址。
  • 內(nèi)存泄漏是指,內(nèi)存資源沒用了,但內(nèi)存資源沒被 free。(用完了,就別占著坑)
  • 在 iOS ARC 時代,大部分內(nèi)存泄漏問題,是由循環(huán)引用造成的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 內(nèi)存管理 簡述OC中內(nèi)存管理機制。與retain配對使用的方法是dealloc還是release,為什么?需要與a...
    丶逐漸閱讀 2,080評論 1 16
  • 1.1 什么是自動引用計數(shù) 概念:在 LLVM 編譯器中設置 ARC(Automaitc Reference Co...
    __silhouette閱讀 5,462評論 1 17
  • 1.遠古時代的故事那些經(jīng)歷過手工管理內(nèi)存(MRC)時代的人們,一定對 iOS 開發(fā)中的內(nèi)存管理記憶猶新。那個時候大...
    MissHector閱讀 242評論 0 1
  • 內(nèi)存管理是程序在運行時分配內(nèi)存、使用內(nèi)存,并在程序完成時釋放內(nèi)存的過程。在Objective-C中,也被看作是在眾...
    蹲瓜閱讀 3,354評論 1 8
  • 初高中英語教科書中大概還會介紹一部根據(jù)小說改編的同名電影<Gone with wind>, 小說的中文譯名是《飄》...
    _levi閱讀 393評論 4 2

友情鏈接更多精彩內(nèi)容