<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
記憶體自動回收機制
,它可以將加入AutoreleasePool
中的變數release
的時機延遲超出其作用域時
立即 release ,如果將其加入到自動釋放池中,這個物件並不會立即釋放,而會等到 runloop 休眠 / 超出autoreleasepool作用域之後
進行釋放從程式啟動到載入完成,主執行緒對應的 Runloop 會處於休眠狀態,等待使用者互動來喚醒 Runloop
使用者每次互動都會啟動一次 Runloop ,用於處理使用者的所有點選、觸控等事件
Runloop 在監聽到互動事件後,就會建立自動釋放池,並將所有延遲釋放的物件新增到自動釋放池中
在一次完整的 Runloop 結束之前,會向自動釋放池中所有物件傳送 release 訊息,然後銷燬自動釋放池
準備簡單程式碼
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"Hello, World!"); } return 0; }
轉換成.cpp檔案:
clang -rewrite-objc main.m -o main.cpp
int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; NSLog((NSString *)&__NSConstantStringImpl__var_folders_jl_d06jlfkj2ws74_5g45kms07m0000gn_T_main_da0d58_mi_0); } return 0; }
__AtAutoreleasePool
型別宣告的程式碼struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} void * atautoreleasepoolobj; };
objc_autoreleasePoolPush
函數,在 結構體退出作用域解構 時呼叫objc_autoreleasePoolPop
函數,這兩個函數也是下邊研究的重點(在 main 函數中 autoreleasepool 處設定斷點檢視組合也可以看到這兩個函數的符號呼叫)ARC
模式// 匯入 _objc_autoreleasePoolPrint 函數,用於列印自動釋放池的結構 extern void _objc_autoreleasePoolPrint(void); int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *objc = [[[NSObject alloc] init] autorelease]; _objc_autoreleasePoolPrint(); } return 0; }
############## AUTORELEASE POOLS for thread 0x1000ebe00 2 releases pending. [0x10700b000] ................ PAGE (hot) (cold) [0x10700b038] ################ POOL 0x10700b038 [0x10700b040] 0x100705f60 NSObject ##############
_objc_autoreleasePoolPrint
呼叫 AutoreleasePoolPage::printAll()(通過AutoreleasePoolPage 的名稱空間呼叫printAll()
);按照自動釋放池的結構,通過雙向連結串列遍歷page
,依次讀取 page 中的內容並進行列印哨兵物件:POOL
和 手動加入自動釋放池 的物件objcPage
資訊,佔56位元組
,因為只有一頁,即是冷頁面,也是熱頁面void * objc_autoreleasePoolPush(void) { // 呼叫 AutoreleasePoolPage 名稱空間下的 push 函數 return AutoreleasePoolPage::push(); }
AutoreleasePoolPage
的定義,能看到這樣一段註釋
/*********************************************************************** Autorelease pool implementation A thread's autorelease pool is a stack of pointers. 執行緒的自動釋放池是一個指標堆疊 Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary. 每個指標要麼是一個要釋放的物件,要麼是POOL_BOUNDARY自動釋放池邊界 A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released. 池令牌是指向該池的POOL_BOUNDARY的指標。當池被彈出,每個比哨兵熱的物件都被釋放 The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary. 堆疊被分成一個雙連結的頁面列表。根據需要新增和刪除頁面 Thread-local storage points to the hot page, where newly autoreleased objects are stored. 執行緒本地儲存指向熱頁,其中儲存新自動釋放的物件 **********************************************************************/
AutoreleasePoolPage 繼承於AutoreleasePoolPageData
(有用的內容基本都在 AutoreleasePoolPageData 結構體中)
class AutoreleasePoolPage : private AutoreleasePoolPageData { friend struct thread_data_t; public: static size_t const SIZE = #if PROTECT_AUTORELEASEPOOL PAGE_MAX_SIZE; // must be multiple of vm page size #else PAGE_MIN_SIZE; // size and alignment, power of 2 #endif private: static pthread_key_t const key = AUTORELEASE_POOL_KEY; static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing static size_t const COUNT = SIZE / sizeof(id); static size_t const MAX_FAULTS = 2; ... }
class AutoreleasePoolPage; struct AutoreleasePoolPageData { #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS struct AutoreleasePoolEntry { uintptr_t ptr: 48; uintptr_t count: 16; static const uintptr_t maxCount = 65535; // 2^16 - 1 }; static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!"); #endif magic_t const magic; __unsafe_unretained id *next; pthread_t const thread; AutoreleasePoolPage * const parent; AutoreleasePoolPage *child; uint32_t const depth; uint32_t hiwat; AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat) : magic(), next(_next), thread(_thread), parent(_parent), child(nil), depth(_depth), hiwat(_hiwat) { } };
結構體中,包含以下成員變數:(根據下邊的成員大小得出 一個page佔56位元組)
magic
:用來校驗 AutoreleasePoolPage 的結構是否完整(16位元組)
next
:指向最新新增的autoreleased
物件的下一個位置,初始化時執行begin()
:獲取物件壓棧的起始位置(8位元組)
thread
:指向當前執行緒(8位元組)
parent
:指向父節點,第一個節點的parent
值為nil
(8位元組)
child
:指向子節點,最後一個節點的child
值為nil
(8位元組)
depth
:代表深度,從0
開始,往後遞增1
(4位元組)
hiwat
:代表high water mark
最大入棧數量標記(4位元組)
static inline void *push() { id *dest; if (slowpath(DebugPoolAllocation)) { // Each autorelease pool starts on a new pool page. dest = autoreleaseNewPage(POOL_BOUNDARY); } else { // dest = autoreleaseFast(POOL_BOUNDARY); } ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; }
DebugPoolAllocation
:當自動釋放池按順序彈出時停止,並允許堆偵錯程式跟蹤自動釋放池autoreleaseNewPage
函數,從一個新的池頁開始建立autoreleaseFast
函數,將哨兵物件壓棧autoreleaseFast
add
函數autoreleaseFullPage
函數遍歷連結串列,找到最後一個空白的子頁面
對其進行建立新頁
設定為熱頁面
新增物件
autoreleaseNoPage
函數呼叫 AutoreleasePoolPage 建構函式,建立新頁begin
:獲取物件壓棧的起始位置(sizeof(*this)
:大小取決於自身結構體中的成員變數、返回物件可壓棧的真正開始地址,在成員變數以下)objc_thread_self
:通過tls
獲取當前執行緒設定為熱頁面
pushExtraBoundary
為YES
,哨兵物件壓棧
物件壓棧
int main(int argc, const char * argv[]) { @autoreleasepool { for (int i = 0; i < 505; i++) { NSObject *objc = [[[NSObject alloc] init] autorelease]; } _objc_autoreleasePoolPrint(); } return 0; } ------------------------- objc[1804]: ############## objc[1804]: AUTORELEASE POOLS for thread 0x1000ebe00 objc[1804]: 506 releases pending. objc[1804]: [0x10200c000] ................ PAGE (full) (cold) objc[1804]: [0x10200c038] ################ POOL 0x10200c038 objc[1804]: [0x10200c040] 0x100638420 NSObject objc[1804]: [0x10200c048] 0x100637a40 NSObject objc[1804]: [0x10200c050] 0x100636970 NSObject ... objc[1804]: [0x100809000] ................ PAGE (hot) objc[1804]: [0x100809038] 0x10063a0b0 NSObject objc[1804]: ##############
504 * 8 = 4032
,加上56位元組
成員變數和8位元組
哨兵物件,共計4096
位元組56位元組
的成員變數void objc_autoreleasePoolPop(void *ctxt) { AutoreleasePoolPage::pop(ctxt); }
static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; //判斷當前物件是否為空預留位置 if (token == (void*)EMPTY_POOL_PLACEHOLDER) { // Popping the top-level placeholder pool. //獲取熱頁面 page = hotPage(); if (!page) { // Pool was never used. Clear the placeholder. //不存在熱頁面,將標記設定為nil return setHotPage(nil); } // Pool was used. Pop its contents normally. // Pool pages remain allocated for re-use as usual. //存在熱頁面,通過雙向連結串列迴圈向上找到最冷頁面 page = coldPage(); //將token設定為起始位置 token = page->begin(); } else { //獲取token所在的頁 page = pageForPointer(token); } //賦值給stop stop = (id *)token; //當前位置不是哨兵物件 if (*stop != POOL_BOUNDARY) { if (stop == page->begin() && !page->parent) { // Start of coldest page may correctly not be POOL_BOUNDARY: // 1. top-level pool is popped, leaving the cold page in place // 2. an object is autoreleased with no pool //最冷頁面的起始可能不是POOL_BOUNDARY: //1. 彈出頂級池,保留冷頁面 //2. 物件在沒有池的情況下被自動釋放 } else { // Error. For bincompat purposes this is not // fatal in executables built with old SDKs. //出現異常情況 return badPop(token); } } if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) { return popPageDebug(token, page, stop); } //出棧 return popPage<false>(token, page, stop); }
static void popPage(void *token, AutoreleasePoolPage *page, id *stop) { if (allowDebug && PrintPoolHiwat) printHiwat(); //當前頁中物件出棧,到stop位置停止 page->releaseUntil(stop); // memory: delete empty children if (allowDebug && DebugPoolAllocation && page->empty()) { // special case: delete everything during page-per-pool debugging //特殊情況:在逐頁池偵錯期間刪除所有內容 //獲取父頁面 AutoreleasePoolPage *parent = page->parent; //銷燬當前頁面 page->kill(); //將父頁面設定為熱頁面 setHotPage(parent); } else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) { // special case: delete everything for pop(top) // when debugging missing autorelease pools //特殊情況:刪除所有的pop //銷燬當前頁面 page->kill(); //將熱頁面標記設定為nil setHotPage(nil); } else if (page->child) { // hysteresis: keep one empty child if page is more than half full //如果頁面超過一半,則保留一個空子頁面 if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); } } }
void releaseUntil(id *stop) { // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage //向下遍歷,到stop停止 while (this->next != stop) { // Restart from hotPage() every time, in case -release // autoreleased more objects //獲取熱頁面 AutoreleasePoolPage *page = hotPage(); // fixme I think this `while` can be `if`, but I can't prove it //如果當前頁面中沒有物件 while (page->empty()) { //獲取父頁面 page = page->parent; //標記為熱頁面 setHotPage(page); } page->unprotect(); #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next; // create an obj with the zeroed out top byte and release that id obj = (id)entry->ptr; int count = (int)entry->count; // grab these before memset #else //記憶體平移,獲取物件 id obj = *--page->next; #endif memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); //當前物件不是哨兵物件 if (obj != POOL_BOUNDARY) { #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS // release count+1 times since it is count of the additional // autoreleases beyond the first one for (int i = 0; i < count + 1; i++) { objc_release(obj); } #else //將其釋放 objc_release(obj); #endif } } //將當前頁面標記為熱頁面 setHotPage(this); #if DEBUG // we expect any children to be completely empty for (AutoreleasePoolPage *page = child; page; page = page->child) { ASSERT(page->empty()); } #endif }
void kill() { // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage AutoreleasePoolPage *page = this; //迴圈找到最後一個子頁面 while (page->child) page = page->child; AutoreleasePoolPage *deathptr; do { deathptr = page; //找到父頁面 page = page->parent; if (page) { //將子頁面設定為nil page->unprotect(); page->child = nil; page->protect(); } //銷燬子頁面 delete deathptr; //遍歷銷燬到this為止 } while (deathptr != this); }
int main(int argc, const char * argv[]) { @autoreleasepool { NSObject *objc = [[[NSObject alloc] init] autorelease]; @autoreleasepool { NSObject *objc = [[[NSObject alloc] init] autorelease]; } _objc_autoreleasePoolPrint(); } return 0; } ------------------------- objc[2511]: ############## objc[2511]: AUTORELEASE POOLS for thread 0x1000ebe00 objc[2511]: 4 releases pending. objc[2511]: [0x10680d000] ................ PAGE (hot) (cold) objc[2511]: [0x10680d038] ################ POOL 0x10680d038 objc[2511]: [0x10680d040] 0x101370c40 NSObject objc[2511]: [0x10680d048] ################ POOL 0x10680d048 objc[2511]: [0x10680d050] 0x101365fb0 NSObject objc[2511]: ##############
結構:
objc_autoreleasePoolPush
函數objc_autoreleasePoolPop
函數特點:
POOL_BOUNDARY
自動釋放池邊界,俗稱:哨兵物件
pop
操作時,需要知道邊界在哪裡,否則會破壞別人的記憶體空間。而哨兵物件,就是邊界標識push
,最先被pop
容量:
4096位元組
,每一頁都包含56位元組
的成員變數,但一個自動釋放池中,只會壓棧一個哨兵物件,佔8位元組
原理:
自動釋放池的本質是__AtAutoreleasePool
結構體,包含建構函式和解構函式
結構體宣告,觸發建構函式,呼叫objc_autoreleasePoolPush
函數,本質是物件壓棧的push
方法
當結構體出作用域空間,觸發解構函式,呼叫objc_autoreleasePoolPop
函數,本質是物件出棧的pop
方法
物件壓棧
page
,並且沒有存滿,呼叫add
函數
*next++
進行記憶體平移page
,但儲存已滿,呼叫autoreleaseFullPage
函數page
,呼叫autoreleaseNoPage
函數AutoreleasePoolPageData
進行初始化begin
:獲取物件壓棧的起始位置objc_thread_self
:通過tls
獲取當前執行緒pushExtraBoundary
為YES
,哨兵物件壓棧物件出棧
popPage
函數,傳入stop
為哨兵物件的位置stop
位置停止kill
函數,銷燬當前頁面巢狀使用:
ARC
模式:
ARC
模式,使用alloc
、new
、copy
、mutableCopy
字首開頭的方法進行物件建立,不會加入到自動釋放池;它們的空間開闢由開發者申請,釋放也由開發者進行管理與執行緒的關係:
autoreleased
物件被放置在當前執行緒的頂部自動釋放池中;當一個執行緒終止時,它會自動清空所有與其關聯的自動釋放池與 Runloop 的關係:
使用:
for (int i = 0; i<100000000; i++) { @autoreleasepool { NSLog(@"%d",i); __autoreleasing LZPerson *p =[LZPerson new]; } }
到此這篇關於iOS底層探索之自動釋放池的文章就介紹到這了,更多相關iOS自動釋放池內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45