How AutoreleasePool works
AutoreleasePool is an automatic memory reclamation mechanism in OC that delays the timing of variable releases added to AutoreleasePool. In simple terms, when an object is created, the variable is normally released as soon as it is out of scope. If you add an object to the autoreleasepool pool, the object will not be released immediately and will not be released until Runloop has gone to sleep/out of autoreleasepool scope {}. The mechanism is shown below
1. From the start of the program to the completion of the load, the runloop corresponding to the main thread is dormant, waiting for user interaction to wake up the runloop
A Runloop is launched for each user interaction, which handles all user clicks, touches, etc
3. After listening for interaction events, Runloop creates an automatic release pool and adds all delayed release objects to the pool
4. Before a full runloop is completed, a release message is sent to all objects in the auto-release pool and the auto-release pool is destroyed
AutoreleasePool structure members and methods
-
Autoreleasepool is essentially a structure object. An autoreleasepool object is a page, which is a stack structure stored in accordance with the principle of first in last out
-
At the bottom of the stack is an empty placeholder of 56 bytes, for a total size of 4096 bytes
-
Only the first page has sentinel objects, storing up to 504 objects, and from the second page up to 505 objects
-
When AutoReleasepool joins the object to be released, the underlying call is the objc_autoreleasePoolPush method
-
The internal implementation of Autoreleasepool when it calls the destructor release is to call the objc_autoreleasePoolPop method
-
The autofree pool is a page structure (mentioned in virtual memory), and the page is a bidirectional linked list (indicating that there are parents and children)
AutoreleasePoolPage
From clang or assembly analysis we know that the bottom of automatic release pool is called objc_autoreleasePoolPush and objc_autoreleasePoolPop two methods, the source code implementation is as follows
/ / * * * * * * * * * * * push method * * * * * * * * * * * void * objc_autoreleasePoolPush (void) {return AutoreleasePoolPage: : push (); } / / * * * * * * * * * * * pop method * * * * * * * * * * * void objc_autoreleasePoolPop (void * CTXT) {AutoreleasePoolPage: : pop (CTXT); }Copy the code
The AutoreleasePoolPage is a page and an object, and the size of the page is 4096 bytes
//************ macro definition ************ #define PAGE_MIN_SIZE PAGE_SIZE #define PAGE_SIZE I386_PGBYTES #define I386_PGBYTES 4096 / * bytes per page * 80386 / / / * * * * * * * * * * * * class definition * * * * * * * * * * * * class AutoreleasePoolPage: private AutoreleasePoolPageData { friend struct thread_data_t; Public: // page SIZE 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: ... // Constructor AutoreleasePoolPage(AutoreleasePoolPage *newParent) : AutoreleasePoolPageData(begin(),// Start storage location objc_thread_self(),// pass the current thread, the current thread through TLS to get newParent, newParent? 1+newParent->depth: 0,// If the first page is 0, then the depth of the previous page is +1 newParent? newParent->hiwat : 0) {... } // Destructor ~AutoreleasePoolPage() {... }... Id * begin() {... } // page end position id * end() {... } // whether the page is empty bool empty() {... } // page full bool full() {... Bool lessThanHalfFull() {... } // add(id obj){... } // releaseAll objects void releaseAll() {... } // Release all objects until stop void releaseUntil(id *stop) {... } // Kill void kill() {... } static void tls_dealloc(void *p) {... } // Get AutoreleasePoolPage static AutoreleasePoolPage *pageForPointer(const void *p) {... } static AutoreleasePoolPage *pageForPointer(uintptr_t p) {... } static inline bool haveEmptyPoolPlaceholder() {... } // Set the empty pool placeholder static inline ID * setEmptyPoolPlaceholder(){... Static inline AutoreleasePoolPage *hotPage(){static inline AutoreleasePoolPage *hotPage(){... } static inline void setHotPage(AutoreleasePoolPage *page) {... } static inline AutoreleasePoolPage *coldPage() {... } static inline id *autoreleaseFast(id obj){... } // Add auto-release object, Static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {static __attribute__((noinline)) ID *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {... Static __attribute__((noinline)) id *autoreleaseNoPage(id obj){... Static __attribute__((noinline)) id *autoreleaseNewPage(id obj) {... } public: // Release static inline ID autorelease(id obj){... } static inline void *push() {... } // Compatible with the old SDK stack method __attribute__((noinline, cold)) static void badPop(void *token){... Template <bool allowDebug> static void popPage(void *token, AutoreleasePoolPage *page, id *stop){... } __attribute__((noinline, cold)) static void popPageDebug(void *token, AutoreleasePoolPage *page, id *stop){... } static inline void pop(void *token){... } static void init(){... } / / print __attribute__ ((noinline, cold)) void print () {... } // printAll __attribute__((noinline, cold)) static void printAll(){... } // printHiwat __attribute__((noinline, cold)) static void printHiwat(){... }Copy the code
AutoreleasePoolPage is derived from AutoreleasePoolPageData, and the attributes of this class are derived from the parent class. The following is the definition of AutoreleasePoolPageData
AutoreleasePoolPageData
AutoreleasePoolPage -> AutoreleasePoolPageData -> AutoreleasePoolPage -> AutoreleasePoolPage From here, it can be shown that the autofree pool is not only a page, but also a bidirectional linked list structure
class AutoreleasePoolPage; Struct AutoreleasePoolPageData {magic_t const magic; __unsafe_unretained id *next; // The location of the newly added autoreleased object points to begin() __unsafe_unretained id *next; Pthread_t const thread; AutoreleasePoolPage * const parent; AutoreleasePoolPage *child; AutoreleasePoolPage *child; Uint32_t const depth; // uint32_t const depth; Uint32_t hiwat; __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) { } };Copy the code
AutoreleasePoolPageData has a memory size of 56 bytes:
-
The type of attribute magic is magic_T structure, which occupies m memory size [4]. Memory occupied (i.e. 4*4=16 bytes)
-
The attributes next, Thread, parent, and Child are all 8 bytes (i.e. 4*8=32 bytes)
-
Uint32_t attribute depth, hiwat type is uint32_t, actual type is unsigned int, both of 4 bytes
objc_autoreleasePoolPush
Enter the objc_autoreleasePoolPush source code implementation with the following logic
- Check whether there is a pool.
- If not, it is created using the autoreleaseNewPage method
- If so, the sentinel object is pushed through autoreleaseFast
Static inline void *push() {id *dest; Slowpath (DebugPoolAllocation)) {// Each autorelease pool starts on a new pool page. // If not, create dest = autoreleaseNewPage(POOL_BOUNDARY); } else {// create an autoreleaseFast(POOL_BOUNDARY); } ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; }Copy the code
Create a page autoreleaseNewPage
- Enter objc_autoreleasePoolPush -> push -> autoreleaseNewPage source code implementation, mainly through hotPage ‘to obtain the current page, determine whether the current page exists
- If so, the object is pushed through the autoreleaseFullPage method
- If not, the page is created using the autoreleaseNoPage method
Static __attribute__((noinline)) id *autoreleaseNewPage(id obj) {AutoreleasePoolPage *page = hotPage(); // If (page) return autoreleaseFullPage(obj, page); Else Return autoreleaseNoPage(obj); } //******** hotPage ******** // Get the current page static inline AutoreleasePoolPage *hotPage() {// Get the current page AutoreleasePoolPage *result = (AutoreleasePoolPage *) tls_get_direct(key); If ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; if (result) result->fastcheck(); return result; } ******** autoreleaseNoPage ******** static __attribute__((noinline)) id *autoreleaseNoPage(id obj) {// "No page" could mean no pool has been pushed // or an empty placeholder pool has been pushed and has no contents yet ASSERT(! hotPage()); bool pushExtraBoundary = false; // Check if it is an empty placeholder, if so, // We are pushing a second pool over the empty placeholder pool pushing the first object into the empty placeholder pool. // Before doing that, push a pool boundary on behalf of the pool // that is currently represented by the empty placeholder. pushExtraBoundary = true; } // If the object is not a sentinel object and there is no Pool, else if (obj! = POOL_BOUNDARY && DebugMissingPools) { // We are pushing an object with no pool in place, // and no-pool debugging was requested by environment. _objc_inform("MISSING POOLS: (%p) Object %p of class %s " "autoreleased with no pool in place - " "just leaking - break on " "objc_autoreleaseNoPool() to debug", objc_thread_self(), (void*)obj, object_getClassName(obj)); objc_autoreleaseNoPool(obj); return nil; Else if (obj == POOL_BOUNDARY &&! // We are pushing a pool with no pool in place, // and alloc-per-pool debugging was not requested. // Install and return the empty pool placeholder. return setEmptyPoolPlaceholder(); // We are pushing an object or a non-placeholder'd pool. // Install the first page AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); // Set page to the current focus page setHotPage(page); // Push a boundary on behalf of the previously allocated pool. // Push a boundary on behalf of the previously allocated pool. If (pushExtraBoundary) {// pushExtraBoundary page->add(POOL_BOUNDARY); } return page->add(obj); }Copy the code
Create page autoreleaseNoPage(new page if no page exists)
AutoreleaseNoPage (); AutoreleasePoolPage (); autoreleaseNoPage (); The constructor is implemented through the initialization method of the parent class AutoreleasePoolPageData (as you can see from the definition above)
**********AutoreleasePoolPage (AutoreleasePoolPage *newParent) : AutoreleasePoolPageData(begin(),// Start storage location objc_thread_self(),// pass the current thread, the current thread through TLS to get newParent, newParent? 1+newParent->depth: 0,// If the first page is 0, then the depth of the previous page is +1 newParent? newParent->hiwat : 0) { if (parent) { parent->check(); ASSERT(! parent->child); parent->unprotect(); Parent ->child = this; parent->protect(); } protect(); } //**********AutoreleasePoolPageData initialization method **********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) { }Copy the code
The meanings of parameters passed to AutoreleasePoolPageData are:
- Begin () indicates the position of the pushdown (that is, the address of the next object to be released). By debugging begin, you can see that its implementation is equal to the header address +56, where 56 is the memory size of the AutoreleasePoolPageData structure
/ / * * * * * * * * the begin () * * * * * * * * / / page starting position id * the begin () {/ / is equal to the first address + 56 (AutoreleasePoolPage memory size of the return (id *) ((uint8_t *)this+sizeof(*this)); }Copy the code
- Objc_thread_self () represents the current thread, which was retrieved via TLS
__attribute__((const)) static inline pthread_t objc_thread_self() {// Get the current thread return via TLS (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF); }Copy the code
-
NewParent indicates the parent node
-
The next two parameters calculate depth and hiwat from the depth of the parent node, the maximum number of pushes
View automatic free pool memory structure
In ARC mode, you cannot manually call autoRelease, so switch Demo to MRC mode (Build Settings -> Objectice -c Automatic Reference Counting set to NO).
- Define the following code
/ / * * * * * * * * * * * * printing automatic release of pool structure * * * * * * * * * * * * extern void _objc_autoreleasePoolPrint (void); / / * * * * * * * * * * * * run code * * * * * * * * * * * * int main (int arg c, const char * argv []) {@ autoreleasepool {/ / cycle to create objects, For (int I = 0; i < 5; i++) { NSObject *objc = [[NSObject alloc] sutorelease]; } // call _objc_autoreleasePoolPrint(); }}Copy the code
The result is 6, but there are only 5 objects in the stack, where the POOL represents the sentry, i.e., the boundary, which is used to prevent crossing the boundary
AutoreleasePoolPage = 56; AutoreleasePoolPage = 56; AutoreleasePoolPage = 56;
Change the data of the above test code to 505, its memory structure is as follows, and find that the first page is full, storing 504 objects to be freed, and the second page stores only one
Change the data to 505+506 to verify that the second page also stores 504 objects
The first page stores 504, the second page stores 505, and the third page stores 2
conclusion
Therefore, the following conclusions can be drawn from the above tests:
-
The first page can hold 504 objects, and only the first page has a sentinel. When a page is full, a new page will be opened
-
From the second page, you can hold up to 505 objects
-
The size of a page is 505 * 8 = 4040
The SIZE of an AutoreleasePoolPage is 4096 bytes. In its constructor, the object is pushed from +56. Therefore, a page can actually store 4096-56 = 4040 bytes, which is converted into objects 4040/8 = 505, that is, a page can store up to 505 objects, among which the first page with sentinel objects can only store 504. Its structure is illustrated below
Interview question: How many sentinels are there in an automatic release pool?
There is only one sentinel object, and the sentinel is on the first page
You can save a maximum of 504 objects on the first page and a maximum of 505 objects at the beginning of the second page
The autoreleaseFast object is pressed
To access the autoreleaseFast source code, there are the following steps:
- Gets the current action page and determines whether the page exists and is full
- If the page exists and is not full, the object is pushed with the add method
- If the page exists and is full, a new page is scheduled using the autoreleaseFullPage method
- If the page does not exist, a new page is created using the autoreleaseNoPage method
Static inline ID *autoreleaseFast(id obj) {AutoreleasePoolPage *page = hotPage(); If (page &&! Page ->full()) {return page->add(obj); Return autoreleaseFullPage(obj, page);} else if (page) {return autoreleaseFullPage(obj, page); } else {return autoreleaseNoPage(obj); }}Copy the code
AutoreleaseFullPage method (if full, schedule a new page)
This method is mainly used to determine whether the current page is full, if the current page is full, through the do-while loop to find the corresponding page of the child node, if not, then create a new page, and push the object
// Add auto-release object, Static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) { // The hot page is full. // Step to the next non-full page, adding a new page if necessary. // Then add the object to that page. ASSERT(page == hotPage()); ASSERT(page->full() || DebugPoolAllocation); If (page->child) page = page->child; if (page->child) page = page->child; Else Page = new AutoreleasePoolPage(page); else page = new AutoreleasePoolPage(page); } while (page->full()); // Set it to the current page setHotPage(page); Return page->add(obj); }Copy the code
As you can see from the AutoreleasePoolPage initialization method, the child object is manipulated to point the current page’s child to the new page, which indicates that the pages are connected by a two-way linked list
The add method
This method is mainly to add the release object, its underlying implementation is to release the object through the next pointer store, and next pointer increment, indicating the next location of the release object store. You can see from this that pages are stored in a stack structure
*add(id obj) {ASSERT(! full()); unprotect(); Id *ret = next; // Next ++ = next; // better than 'return next-1' because of aliasing // next++ = next; protect(); return ret; }Copy the code
Autorelease underlying analysis
In demo, we use the autorelease method, in MRC mode, to push the object into the automatic release pool, the following analysis of its underlying implementation
Check the source code for the AutoRelease method
- If it is not an object or a small object, it returns directly
- If it is an object, the autoRelease of the object is called torelease it
__attribute__((aligned(16), flatten, noinline)) id objC_AutoRelease (id obj) {// If not an object, return if (! obj) return obj; If (obj->isTaggedPointer()) return obj; if (obj->isTaggedPointer()) return obj; return obj->autorelease(); }Copy the code
Go to the autoRelease implementation of the object
inline id objc_object::autorelease() { ASSERT(! isTaggedPointer()); If (fastPath (! ISA()->hasCustomRR())) { return rootAutorelease(); } return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease)); } Inline ID objc_Object ::rootAutorelease() {if (isTaggedPointer()) return (id)this; if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this; return rootAutorelease2(); } __attribute__((noinline,used)) id objc_object::rootAutorelease2() { ASSERT(! isTaggedPointer()); return AutoreleasePoolPage::autorelease((id)this); } static inline id autorelease(id obj) { ASSERT(obj); ASSERT(! obj->isTaggedPointer()); //autoreleaseFast stack operation id *dest __unused = autoreleaseFast(obj); ASSERT(! dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); return obj; }Copy the code
As you can see from this point, both the pushed sentinel object and the normal object come to the autoreleaseFast method, but with different identifiers
objc_autoreleasePoolPop
There is a parameter in the objc_autoreleasePoolPop method. Clang analysis found that the parameter passed is the sentinel object returned after the push, namely CTXT. The purpose is to avoid stack clutter and prevent other objects from being pushed off the stack
Enter the pop source code implementation, mainly by the following steps
- Empty page processing, and obtain the page according to the token
- Fault-tolerant processing
- PopPage out of the stack
Static inline void pop(void *token) {AutoreleasePoolPage *page; id *stop; If (token == (void*)EMPTY_POOL_PLACEHOLDER) {// If (token == (void*)EMPTY_POOL_PLACEHOLDER) { // Get current page = hotPage(); if (! // Pool was never used. Clear the placeholder. // If the current page does not exist, return setHotPage(nil); } // Pool pages remain remain of allocated tokens for re-use as usual. // If the current page exists, Then set the current page to coldPage and the token to the start of coldPage = coldPage(); token = page->begin(); } else {page = pageForPointer(token); } stop = (id *)token; // Check whether the last position is sentinel if (*stop! If (stop == page->begin() &&! Page ->parent) {// If it is the first position, and there is no parent, do nothing // Start of page may be 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} else {// If it is the first place, and there is a parent node, 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); }Copy the code
AllowDebug is false. Release until all objects in the stack up to the current page stop position are released, that is, a release message is sent to objects in the stack until the incoming sentry object is encountered
Template <bool allowDebug> static void popPage(void *token, AutoreleasePoolPage *page, id *stop) { if (allowDebug && PrintPoolHiwat) printHiwat(); Page ->releaseUntil(stop); If (allowDebug && DebugPoolAllocation && page->empty()) {// Special case: Delete everything during page-per-pool debugging // Delete all pools in each special case during debugging // Get the parent node of the current page AutoreleasePoolPage *parent = page->parent; // Kill the current page->kill(); // set the operation page to the parent node page setHotPage(parent); } else if (allowDebug && DebugMissingPools && page->empty() && ! page->parent) { // special case: Delete everything for pop(top) // when debugging missing autorelease pools Delete all contents of pop (top) when debugging lost autorelease pool page->kill(); setHotPage(nil); } else if (page->child) { // hysteresis: Keep one empty child if the page is more than half full If (page->lessThanHalfFull()) {page->child->kill(); } else if (page->child->child) { page->child->child->kill(); }}}Copy the code
The releaseUntil implementation is implemented to determine if an object is equal to stop by looping through it. The goal is to release all objects before stop
- The object is first released by getting next for the page (that is, the last object in the page), and decrement next to get the last object
- Determine if it is a sentinel object, and if not, automatically call objc_release to release it
Void releaseUntil(id *stop) {// Not recursive: We don't want to blow out the stack: // If a thread accumulates a stupendous amount of garbage While (this->next! = stop) {// start hotPage() every time, in case -release // autorelease more objects AutoreleasePoolPage *page = hotPage(); // fixme I think this `while` can be `if`, // If the current page is empty while (page->empty()) {// Assign page to parent page = page->parent; // Set the current page to the parent page setHotPage(page); } page->unprotect(); Obj = *--page->next; Memset ((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); if (obj ! = POOL_BOUNDARY) {// Release objc_release(obj); }} // Set the current page setHotPage(this); #if DEBUG // we expect any children to be completely empty for (AutoreleasePoolPage *page = child; page; page = page->child) { ASSERT(page->empty()); } #endif }Copy the code
The kill implementation basically destroys the current page, assigns the current page to the parent page, and sets the child object pointer of the parent page to nil
// Destroy 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; // The child node becomes the parent node page = page->parent; if (page) { page->unprotect(); Page ->child = nil; page->protect(); } delete deathptr; } while (deathptr ! = this); }Copy the code
conclusion
Based on the above analysis, for the automatic release pool push and POP, the summary is as follows:
In a pushdown (that is, push) operation that automatically releases the pool
- When there is no pool, that is, only empty placeholders (stored in TLS), the page is created and the sentinel object is pushed
- Pushing normal objects in a page is done primarily by incrementing the next pointer,
- When the page is full, you need to set the child object of the page to the new page
In an out of the stack (that is, pop) operation of the auto release pool
- Pushing ordinary objects out of a page is done primarily by decrement the next pointer,
- When the page is empty, the parent object of the page needs to be assigned to the current page