漫談內(nèi)存泄漏

前言

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

什么是內(nèi)存

在計(jì)算機(jī)系統(tǒng)中,我們談?wù)摰?strong>內(nèi)存通常是指DRAM

虛擬內(nèi)存空間

而當(dāng)我們程序在系統(tǒng)上運(yùn)行起來(lái)時(shí),操作系統(tǒng)為我們提供了一個(gè)假象,程序看起來(lái)是獨(dú)占使用處理器、主存和I/O設(shè)備的。這種假象是通過(guò)進(jìn)程的概念來(lái)實(shí)現(xiàn)的。

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

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

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

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

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

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

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

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

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

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

ARC 機(jī)制

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

舉個(gè)簡(jiǎn)單的例子:

場(chǎng)景一

self.textField.text = @"Sim";

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

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

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

2.png

場(chǎng)景二

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

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

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

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

4.png

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

5.png

循環(huán)引用

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

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

6.png

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

一般情況下,ViewController 是會(huì)被個(gè) UINavigationController 所持有。如果 TableView 也持有 ViewController ,這時(shí) ViewController 的 retainCount = 2。

而我們對(duì) self.vc 使用了 weak 后,self.vc = ViewController ,這樣的操作,不再會(huì)導(dǎo)致 retainCount 加1 。 這時(shí) ViewController 依然還是 retainCount = 1。

7.png

而當(dāng) ViewController 被釋放后 self.vc 建會(huì)指向 nil 。當(dāng)然在這個(gè)例子,在 ViewController 釋放后,TableView 自然也會(huì)別釋放。

8.png

但在一些場(chǎng)景下使用 weak 需要比較注意的。例如,一個(gè)全局的定時(shí)器,如果持有了 ViewController 是弱引用。 那當(dāng) ViewController 被釋放后,定時(shí)器再去訪問(wèn) ViewController 就將引起 crash 。

常見(jiàn)的內(nèi)存泄漏場(chǎng)景

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

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

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

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

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

總結(jié)

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

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

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

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