關於內存管理的總結

每一種編程語言在使用的過程中,都會開闢內存空間,常用的兩種存儲結構是棧區和堆區。這兩種結構的使用特點和分配方式各有差異(在前面圖吧導航引擎組的電面總結博客裏已經淺析了他們的區別),內存管理所說的管理,大是管理的堆區,因爲堆是由程序員自己申請空間大小,自己手動釋放,可操作性比較強;而棧區是由系統自動分配,程序結束後自動釋放內存,程序員無法控制。

我所接觸到的內存管理的方式有以下幾種:MRC、ARC、GC、手動管理(malloc\free、new\delete)、虛擬內存;

在iOS開發過程中,OC使用的是MRC\ARC,Swift使用的是ARC;
Java使用的是GC(垃圾回收機制);
C/C++使用的是手動管理的方式,C:malloc\free;C++:new\delete
計算機管理物理內存使用的是設置虛擬內存的方法;

展開來講:

爲什麼要進行內存管理?

由於移動設備的內存極其有限,所以每個APP所佔的內存也是有限制的,當app所佔用的內存較多時,系統就會發出內存警告,這時需要回收一些不需要再繼續使用的內存空間,比如回收一些不再使用的對象和變量等。

管理範圍:任何繼承NSObject的對象,對其他的基本數據類型無效。

本質原因是因爲對象和其他數據類型在系統中的存儲空間不一樣,其它局部變量主要存放於棧中,而對象存儲於堆中,當代碼塊結束時這個代碼塊中涉及的所有局部變量會被回收,指向對象的指針也被回收,此時對象已經沒有指針指向,但依然存在於內存中,造成內存泄露。

IOS中的內存管理:

MRC(MannulReference Counting):手動引用計數。

是在我們申請到某一塊內存,在使用之後,要手動釋放,釋放機理涉及到計數器問題,在創建了一個對象之後,計數器自動設爲1,如果要調用這個對象,要做retain操作,使計數器+1,使用完成後要做release,使計數器-1,當retainCount 的值爲0事,釋放該對象所佔的內存。如果未釋放內存,會造成內存的浪費,俗稱內存泄露,

MRC的工作原理(工作過程):在創建對象時,使用alloc方法使引用計數器(retainCount )爲1,調用該對象時,要做retain操作,使引用計數器+1,使用完該對象後,要做release操作,使引用計數器-1,最後當引用計數器(retainCount )爲0時,系統會自動向對象發送一條dealloc消息,一般會重寫dealloc方法,調用[super dealloc],在此處釋放內存。

與對變量的管理相關的主要方法有:retain,release,autorelease;

1.和內存管理相關的方法
1)alloc 引用計數器自動設爲1
2)retain 引用計數器+1 返回了經過+1以後的當前實例對象
3)release 引用計數器-1,並不一定是釋放
4)retainCount 獲取引用計數器的值,當值爲0時,那麼該對象將被銷燬,其佔用的內存被系統回收。
5)dealloc 當實例對象被銷燬之前,系統自動調用。
一定要調[super dealloc]
6)Autorelease 將對象放入一個自動釋放池中,當自動釋放池被銷燬時,會對池中所有對象做一次release。佔用內存較大的對象不宜用此方法。

示例代碼:

//假設Number爲預定義的類

Number* num = [[Number alloc] init];

Number* num2 = [num retain];//此時引用記數+1,現爲2

[num2 release]; //num2 釋放對內存數據的所有權 引用記數-1,現爲1;

[num release];//num釋放對內存數據的所有權 引用記數-1,現爲0;

[num add:1 and 2];//bug,此時內存已釋放。

當對象被銷燬時,系統會自動向對象發送一條dealloc消息,一般會重寫dealloc方法,在這裏釋放相關的資源,dealloc就像是對象的“臨終遺言”。一旦重寫了dealloc方法就必須調用[super dealloc],並且放在代碼塊的最後調用(不能直接調用dealloc方法)。

dealloc方法的代碼規範

(1)一定要[super dealloc],而且要放到最後

(2)對self(當前)所擁有的的其他對象做一次release操作

-(void)dealloc

{

[_car release];

[super dealloc];

}

//autoreleasepool 的使用 在MRC管理模式下,我們摒棄以前的用法,NSAutoreleasePool對象的使用,新手段爲@autoreleasepool

@autoreleasepool {

       Number* num = [[Number alloc] init];

              [numautorelease];//由autoreleasepool來管理其內存的釋放

   }

對與Objective-c中屬性的標識符可以總結爲:

@property (nonatomic/atomic,retain/assign/copy, readonly/readwrite) Number* num;

(1) nonatomic/atomic,表示該屬性是否是對多線程安全的,是不是使用線程鎖,默認爲atomic,

(2) retain/assign/copy,是有關對該屬性的內存管理的,

和內存管理相關的名詞
1)殭屍對象:此對象被銷燬,不能再使用,不能給它發送任何消息
2)野指針:指向殭屍對象(不可用的內存)的指針,給野指針發送消息將會產生不可控的後果。
3)空指針:沒有指向任何對象的指針,給空指針發消息不會產生任何行爲

內存管理原則

(一)原則

只要還有人在使用某個對象,那麼這個對象就不會被回收;

只要你想使用這個對象,那麼就應該讓這個對象的引用計數器+1;

當你不想使用這個對象時,應該讓對象的引用計數器-1;

(二)誰創建,誰release

(1)如果你通過alloc,new,copy來創建了一個對象,那麼你就必須調用release或者autorelease方法

(2)不是你創建的就不用你去負責

(三)誰retain,誰release

只要你調用了retain,無論這個對象時如何生成的,你都要調用release

(四)總結

有始有終,有加就應該有減。曾經讓某個對象計數器加1,就應該讓其在最後-1.

ARC(AutomaticReference Counting):自動引用計數;

ARC的工作原理還是MRC的工作原理,只不過是原先需要手動處理內存管理的引用計數器的代碼全部由編譯器來替我們完成。

ARC使得開發者在使用alloc申請了一塊內存之後,其他的都交給運行器自動管理,開發者不需要再考慮何時使用retain、release、autorelease這樣的函數來管理內存,它提供了自動評估內存生存期的功能,並且在編譯器中自動加入合適的管理內存的方法。編譯器也會自動生成dealloc函數。對程序員來說平時工作強推ARC,但是瞭解MRC的工作原理也是十分必要的。

在ARC中與內存管理有關的標識符,可以分爲變量標識符和屬性標識符,對於變量默認爲__strong,而對於屬性默認爲unsafe_unretained。也存在autoreleasepool。

對於變量的標識符有:

(1) __strong,is the default. An object remains “alive” as long as there is a strong pointerto it.

(2) __weak,specifies a reference that does not keep the referenced object alive. A weakreference is set to nil when there are no strong references to the object.

(3)__unsafe_unretained,specifies a reference that does not keep the referenced object alive and is notset to nil when there are no strong references to the object. If the object itreferences is deallocated, the pointer is left dangling.

(4)__autoreleasing,is used to denote arguments that are passed by reference (id *) and areautoreleased on return,managedby Autoreleasepool.

對於變量標識符的用法:

__strong Number* num = [[Number alloc]init];

在ARC內存管理模式下,其屬性的標識符存在以下幾種:

@property (nonatomic/atomic, assign/retain/strong/weak/unsafe_unretained/copy,readonly/readwrite) Number* num;//默認爲unsafe_unretained

其中assign/retain/copy與MRC下property的標識符意義相同,strong類似與retain,assign類似於unsafe_unretained,strong/weak/unsafe_unretained與ARC下變量標識符意義相同,只是一個用於屬性的標識,一個用於變量的標識(帶兩個下劃短線__)。所列出的其他的標識符與MRC下意義相同。

(1)對於assign,你可以對標量類型(如int)使用這個屬性。你可以想象一個float,它不是一個對象,所以它不能retain、copy。

(2)對於copy,指定應該使用對象的副本(深度複製),前一個值發送一條release消息。基本上像retain,但是沒有增加引用計數,是分配一塊新的內存來放置它。特別適用於NSString,如果你不想改變現有的,就用這個,因爲NSMutableString,也是NSString。

ARC的特點總結:

(1)不允許調用release,retain,retainCount ,autorelease

(2)不允許重寫dealloc,但是不允許調用[super dealloc]

(3)@property的參數:

Strong:相當於原來的retain(適用於OC對象類型),成員變量是強指針

Weak:相當於原來的assign,(適用於oc對象類型),成員變量是弱指針

Assign:適用於非OC對象類型(基礎類型)

4)不可以使用NSAllocateObject\NSDeallocateObject\NSAutoreleasePoll

5)作爲替代,@autoreleasepool被引入,可以使用這個效率更高的關鍵字;

6)當使用alloc申請了一塊內存之後,其他的都交給運行器自動管理了。

使用ARC可能出現的循環引用問題:

所有的自動引用計數機制都有一個從理論上無法繞過的限制,那就是循環引用 (retain cycle) 的情況。

這裏寫圖片描述

object1和object2之間形成了循環引用,它們的引用計數始終爲1,始終不會被釋放,這就造成了內存泄漏。

示例代碼:

假設我們有兩個類 A 和 B , 它們之中分別有一個存儲屬性持有對方:

class A {
  let b: B
  init() {
    b = B()
    b.a = self
  }
  deinit {
    println("A deinit")
  }
}
class B {
  var a: A? = nil
  deinit {
    println("B deinit")
  }
}

在 A 的初始化方法中,我們生成了一個 B 的實例並將其存儲在屬性中。然後我們又將 A 的實例賦值給了 b.a 。這樣 a.b 和 b.a 將在初始化的時候形成一個引用循環。現在當有第三方的調用初始化了 A ,然後即使立即將其釋放, A 和 B 兩個類實例的 deinit 方法也不會被調用,說明它們並沒有被釋放。

func application(application: UIApplication!, 
         didFinishLaunchingWithOptions launchOptions: NSDictionary!) 
         -> Bool 
{
  // Override point for customization after application launch.
  var obj: A? = A()
  obj = nil
  // 內存沒有釋放
  return true
}

因爲即使 obj 不再持有 A 的這個對象,b 中的 b.a 依然引用着這個對象,導致它無法釋放。而進一步,a 中也持有着 b,導致 b 也無法釋放。在將 obj 設爲 nil 之後,我們在代碼裏再也拿不到對於這個對象的引用了,所以除非是殺掉整個進程,我們已經 永遠 也無法將它釋放了。多麼悲傷的故事啊..

**

循環引用解決辦法:

**

爲了防止這種人神共憤的悲劇的發生,我們必須給編譯器一點提示,表明我們不希望它們互相持有。一般來說我們習慣希望 “被動” 的一方不要去持有 “主動” 的一方。在這裏 b.a 裏對 A 的實例的持有是由 A 的方法設定的,我們在之後直接使用的也是 A 的實例,因此認爲 b 是被動的一方。可以將上面的 class B 的聲明改爲:

class B {
    weak var a: A? = nil
    deinit {
        println("B deinit")
    }
}

在 var a 前面加上了 weak ,向編譯器說明我們不希望持有 a。這時,當 obj 指向 nil 時,整個環境中就沒有對 A 的這個實例的持有了,於是這個實例可以得到釋放。接着,這個被釋放的實例上對 b 的引用 a.b 也隨着這次釋放結束了作用域,所以 b 的引用也將歸零,得到釋放。添加 weak 後的輸出:

A deinit
B deinit

以上所講的是OC和Swift中使用的兩種內存管理辦法ARC和MRC,剩下的兩種內存管理辦法,C和C++的內存管理和虛擬內存的相關知識在我前面的一篇圖吧導航引擎組電面總結的博客裏有詳細說明,有興趣的朋友可以翻閱查看,如有不足之處,望留言批評指正。

發佈了38 篇原創文章 · 獲贊 15 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章