如果有不好的地方或者不全面的地方請留言批評指正,拜謝~~~
引發(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):
)
看圖表示可以得知:真正引用計數(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/