iOS內(nèi)存管理筆記

如果有不好的地方或者不全面的地方請留言批評指正,拜謝~~~


引發(fā)反思
棧怎么清除?會引發(fā)什么狀況?怎么使棧溢出?
堆空間怎么清除?會引發(fā)什么狀況?怎么使堆溢出?

Class A{

    NSString *_name;
    NSString *_xxxx;
    int       _age;
    A        *superClass;
}
A *a = [[A alloc] init];

內(nèi)存是這么分配的?
a代表什么?

內(nèi)存管理總結(jié)

內(nèi)容分為虛擬內(nèi)存+物理內(nèi)存,正常初始化空間分配的都是虛擬內(nèi)存,初始化實例才會占用物理內(nèi)容
Instrument的Allocations工具可以監(jiān)控
All Heap & Anoymous VM代表虛擬內(nèi)存
Dirty Memory,resident Size就是物理內(nèi)存的大小

訪問內(nèi)存其實都是訪問的邏輯地址,需要轉(zhuǎn)換后才能訪問物理內(nèi)存,
CPU根據(jù)邏輯地址通過界限寄存器判斷是否越界,越界地址錯誤,反之加上基址寄存器轉(zhuǎn)換成物理內(nèi)存地址

iOS內(nèi)存過多而被Kill掉?基于什么原則?

iOS使用的是低內(nèi)存處理機制Jetsam,基于優(yōu)先級隊列的機制。
當(dāng)內(nèi)存過低的時候,就會在隊列中進行廣播,希望大家盡量釋放內(nèi)存,如果一段時間后,仍然內(nèi)存不夠,就會開始Kill進程,直到內(nèi)存夠用。

介紹下內(nèi)存的幾大區(qū)域?

1、棧:局部變量(基本數(shù)據(jù)類型、指針變量)當(dāng)期作用域執(zhí)行完畢之后,就會被系統(tǒng)立即收回(無需程序員管理(分配地址由高到低分配))

2、堆:程序運行的過程中動態(tài)分配的存儲空間(創(chuàng)建的對象),手動申請的字節(jié)空間需要調(diào)用free來釋放

3、Bss段:沒有初始化的全局變量和靜態(tài)變量,一旦初始化就會從BSS段中收回掉,轉(zhuǎn)存到數(shù)據(jù)段中

4、數(shù)據(jù)段:存放已經(jīng)初始化的全局變量和靜態(tài)變量,以及常量數(shù)據(jù),直到程序結(jié)束才會被立即收回

5、代碼段:程序編譯后的代碼內(nèi)容,直到結(jié)束程序才會被收回

iOS內(nèi)存中的對象主要有兩類,
一類是值類型,比如int、float、struct等基本數(shù)據(jù)類型
一類是引用類型,也就是繼承NSObject類的所有OC對象
值類型會被放入棧中,遵循先進后出的原則
引用類型會放在堆中,當(dāng)給對象分配內(nèi)存空間時,對隨機在內(nèi)存中開辟空間。

為什么會循環(huán)引用

當(dāng)兩個不同的對象各有一個強引用指向?qū)Ψ?,那么循環(huán)引用就產(chǎn)生了,每個對象的引用計數(shù)都會+1,無法得到內(nèi)存的釋放
weak是弱引用,計數(shù)器不會加一,并在引用對象被釋放的時候自動設(shè)置為nil

unsafe_unretained , weak, assign 區(qū)別

  • __unsafe_unretained: 不會對對象進行retain,當(dāng)對象銷毀時,會依然指向之前的內(nèi)存空間(野指針)
  • __weak: 不會對對象進行retain,當(dāng)對象銷毀時,會自動指向nil
  • assign:用于對基本數(shù)據(jù)類型進行賦值操作,不更改引用計數(shù)(基本類型內(nèi)存分配棧上,系統(tǒng)自動處理)。如果來修飾對象,被assgin修飾的對象在釋放后,指針的地址還是存在的,成為野指針。如果后續(xù)分配在堆上的內(nèi)存正好在這個地址上程序就會crash。
  • copy:在修飾Mutable可變類型會在內(nèi)存里拷貝一份對象,兩個指針指向不同的內(nèi)存地址。copy出來的新對象是不可變類型的。而修飾NSString、NSArray等普通類型,充當(dāng)strong使用,內(nèi)存計數(shù)+1
  • strong:強指針,指向?qū)ο髢?nèi)存地址,內(nèi)存計數(shù)+1
  • nonatomic:非原子屬性。它的特點是多線程并發(fā)訪問性能高,但是訪問不安全
  • atomic:原子性,線程安全的,setter方法加鎖

野指針是什么,iOS 開發(fā)中什么情況下會有野指針?

指針是不為nil,但是指向已經(jīng)被釋放的內(nèi)存的指針。
__unsafe_unretain或者assign的指針,對象釋放后會出現(xiàn)野指針。
一般情況下oc使用了weak指針,在對象銷毀時指針會置nil

block內(nèi)存相關(guān)

問:__block什么時候用?
答:在block里面修改局部變量的值都要用__block修飾

問:在block里面, 對數(shù)組執(zhí)行添加操作, 這個數(shù)組需要聲明成__block嗎?
答:不需要聲明成__block,因為testArr數(shù)組的指針并沒有變(往數(shù)組里面添加對象,指針是沒變的,只是指針指向的內(nèi)存里面的內(nèi)容變了)

問:在block里面, 對NSInteger進行修改, 這個NSInteger是否需要聲明成__blcok ?
答:NSInteger的值發(fā)生改變,則要求添加__block修飾

block變量定義時為什么用copy?block是放在哪里的?

默認(rèn)情況下,block是存檔在棧中,可能被隨時回收,通過copy操作可以使其在堆中保留一份, 相當(dāng)于一直強引用著, 因此如果block中用到self時, 需要將其弱化, 通過__weak或者__unsafe_unretained

autoreleasepool的使用場景和原理

自動釋放池是OC中內(nèi)存自動回收機制,它可以延遲加入autoreleasepool中變量release的時機,創(chuàng)建的變量會在超出其作用域的時候release

autorelease本質(zhì)上就是延遲調(diào)用release

在沒有手加Autorelease Pool的情況下,Autorelease對象是在當(dāng)前的runloop迭代結(jié)束時釋放的,而它能夠釋放的原因是系統(tǒng)在每個runloop迭代中都加入了自動釋放池Push和Pop(每一個線程都有一個默認(rèn)autoreleasepool)

棧 - 泄露

int number = 4;
int *a = malloc(8);
a = &number;
free(a);

給指針a分配了8個字節(jié)的地址,a又指向了number的地址,最后a釋放了。這時候釋放的是number的地址,而number是在棧區(qū)中,不能被手動釋放,這時候就出現(xiàn)了棧的內(nèi)存泄露

weak、strong在內(nèi)存的作用

默認(rèn)情況下,一個指針都會使用__strong屬性,表明這是一個強引用。當(dāng)所有強引用都去除時,對象才能被釋放

但有時候我們可能要禁止這種行為:一些集合類不應(yīng)該增加其元素的引用,可能對導(dǎo)致對象無法釋放,可能會出現(xiàn)循環(huán)引用,導(dǎo)致無法釋放,這種情況下我們要使用弱引用__weak。

weak是弱引用,計數(shù)器不會加一,并在引用對象被釋放的時候自動設(shè)置為nil

線程中棧與堆是公有的還是私有的

在多線程環(huán)境下,每個線程擁有一個棧和程序計數(shù)器,棧和程序計數(shù)器用來保存線程執(zhí)行歷史和線程執(zhí)行的狀態(tài),是線程私有資源

堆資源是統(tǒng)一進程內(nèi)多線程共享的

OC的內(nèi)存是怎么管理的?

一般來說我們可能會說“使用引用計數(shù)管理”,是的沒錯,但是引用計數(shù)是如何管理的呢?

2013年9月,蘋果推出了iPhone5s,推出TaggedPointer概念,為了節(jié)省內(nèi)存和提高執(zhí)行效率。
NSNumber,NSString,NSData等一些較小的數(shù)據(jù)TaggedPointer將一個對象的指針拆成兩部分,一部分直接保存數(shù)據(jù),另一部分作為特殊標(biāo)記,表示這是一個特別的指針,不指向任何一個地址

正常Object對象還是會使用引用計數(shù)來管理,那么蘋果是這么管理的呢?

為了管理內(nèi)存蘋果內(nèi)置了全局的SideTables(散列表),存儲的是SideTable的結(jié)構(gòu)體。

   SideTable 結(jié)構(gòu)體
     struct SideTable{
        //保證原子操作的自旋鎖
        spinlock_t slock;
        //引用計數(shù)的hash表
        RefcountMap refcnts;
        //weak 引用全局hash表
        weak_table_t weak_table;
    }

網(wǎng)上有個例子這里:

100個學(xué)生(對象)住宿,大家都在一個樓上(SideTables),有10個房間(SideTable),每個房間8個學(xué)生(obj);
關(guān)系類似于此

spinlock_t:自旋鎖,如果已經(jīng)在訪問100號宿舍的某個學(xué)生,那么在這時候是其他線程是無法訪問100號宿舍的其他學(xué)生,自旋鎖比較適用于鎖使用者保持鎖時間比較短的情況

RefcountMap:對象具體的引用計數(shù),沒錯就是他,所有strong等可以是引用計數(shù)+1的操作都會在這里標(biāo)記,
因為一個SideTable可能有多個對象的計數(shù)器,SideTables[0x0000]和SideTables[0x0x000f] 可能都是同一個SideTable,所以蘋果又提供了table.refcnts.find[0x0x000f]來找到真正的引用計數(shù)器
計數(shù)器的存儲結(jié)構(gòu):

image

)
看圖表示可以得知:真正引用計數(shù)器是從第三位開始,也就是4的位置。
如果這里引用計數(shù)為0了,就會直接執(zhí)行dealloc,查看是否有weak引用會把weak_table_t中的弱引用置nil

weak_table_t:

 struct weak_table_t {
    // 保存了所有指向指定對象的 weak 指針(數(shù)組)
    weak_entry_t *weak_entries;
    // 存儲空間
    size_t    num_entries;
    // 參與判斷引用計數(shù)輔助量
    uintptr_t mask;
    // hash key 最大偏移值
    uintptr_t max_hash_displacement;
};

weak_entries:通過循環(huán)遍歷來找到對應(yīng)的entry。

struct weak_entry_t {
//被指對象的地址。前面循環(huán)遍歷查找的時候就是判斷目標(biāo)地址是否和他相等
DisguisedPtrobjc_object> referent;
union {
    struct {
        //可變數(shù)組,里面保存著所有指向這個對象的弱引用的地址。當(dāng)這個對象被釋放的時候,referrers里的所有指針都會被設(shè)置成nil。
        weak_referrer_t *referrers;
        uintptr_t        out_of_line : 1;
        uintptr_t        num_refs : PTR_MINUS_1;
        uintptr_t        mask;
        uintptr_t        max_hash_displacement;
    };
    struct {
        //只有WEAK_INLINE_COUNT個元素的數(shù)組,默認(rèn)情況下用它來存儲弱引用的指針。當(dāng)大于4個的時候使用referrers來存儲指針。
        weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
    };
 }
}

weak的原理

為了管理所有的引用計數(shù)和weak指針,蘋果創(chuàng)建了一個全局的SideTables,它是一個全局的hash表,用于存儲指向某個對象的所有weak指針,key是所指向?qū)ο蟮牡刂?,value是weak指針的地址數(shù)組,里面存放的都是SideTable結(jié)構(gòu)體

weak是弱引用,計數(shù)器不會加一,并在引用對象被釋放的時候自動設(shè)置為nil

對象引用計數(shù)相關(guān)的操作是原子性的,如果多個線程同事操作一個對象的引用計數(shù)會造成數(shù)據(jù)錯亂,同時在內(nèi)存中的對象數(shù)據(jù)量大,不能讀整個Hash加鎖,所以蘋果采用了分離鎖

步驟:
1、初始化時,runtime會調(diào)用obj_initWeak函數(shù),初始化一個新的weak指針指向?qū)ο蟮牡刂?/p>

NSObject *obj = [[NSObject alloc] init];
id __weak obj1 = obj;

初始化weak變量的時候,runtime會調(diào)用NSObject.mm中的obj_initWeak函數(shù),函數(shù)聲明如下:

id objc_initWeak(id *object, id value);

而對于objc_initweak()方法的實現(xiàn)

id objc_initWeak(id *location, id newObj) {
// 查看對象實例是否有效
// 無效對象直接導(dǎo)致指針釋放
    if (!newObj) {
        *location = nil;
        return nil;
    }
    // 這里傳遞了三個 bool 數(shù)值
    // 使用 template 進行常量參數(shù)傳遞是為了優(yōu)化性能
    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
    (location, (objc_object*)newObj);
}

2、添加引用時,obj_initWeak函數(shù)會調(diào)用obj_storeWeak()函數(shù)更新指針指向,創(chuàng)建對應(yīng)的弱引用表

obj_storeWeak()函數(shù)聲明

id objc_storeWeak(id *location, id value);

代碼具體看:
http://www.cocoachina.com/ios/20170328/18962.html
https://www.desgard.com/weak/

3、釋放時,調(diào)用clearDeallocating函數(shù),首先根據(jù)對象地址獲取到所有的weak指針地址的數(shù)組,然后遍歷這個數(shù)組把其中的數(shù)據(jù)設(shè)為nil,最后把這個entry從weak表中刪除,最后清理對象的記錄
http://www.cocoachina.com/ios/20170328/18962.html

相關(guān)鏈接:
http://sindrilin.com/runtime/2016/12/23/閑聊內(nèi)存管理
http://zhoulingyu.com/2017/02/15/Advanced-iOS-Study-objc-Memory-2/
https://www.aliyun.com/jiaocheng/topic_39068.html
http://www.cocoachina.com/ios/20150605/11990.html
http://ios.jobbole.com/89012/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,282評論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,684評論 1 32
  • OC語言基礎(chǔ) 1.類與對象 類方法 OC的類方法只有2種:靜態(tài)方法和實例方法兩種 在OC中,只要方法聲明在@int...
    奇異果好補閱讀 4,532評論 0 11
  • 內(nèi)存管理 簡述OC中內(nèi)存管理機制。與retain配對使用的方法是dealloc還是release,為什么?需要與a...
    丶逐漸閱讀 2,086評論 1 16
  • 隨著中國社會政治經(jīng)濟的快速發(fā)展,越來越多的青壯年農(nóng)民走入城市,在廣大農(nóng)村也隨之產(chǎn)生了一個特殊的未成年人群體——農(nóng)村...
    南瓜君吖閱讀 410評論 0 10

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