Automatically free up the pool’s memory management

First let’s look at the following code in action

You can see the constant increase in inner existence

Now we add an automatic release pool inside the for loop

As you can see, with the auto release pool, memory is basically stable at the same value

From the above comparison, it can be found that automatic release pool can manage the memory of created objects and release objects in time when they are used up

Take a look at the following code in action

As you can see from the execution of the code above, the creation of basic data types, alloc/init, new, copy-initialized objects, and isTaggedPointer objects does not cause an increase in memory because variables created in these ways are not managed by the automatic release pool. Init, new, and copy initialized objects are managed by ARC. The isTaggedPointer small object type does not need to be managed in memory. Its value is stored in a pointer. All objects other than the small objects mentioned above and those initialized with alloc/init, new, and copy can be managed by automatic release pooling.

Principle of automatic release pool

We use clang to compile the main.m file and get a main. CPP file

xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main.cpp
Copy the code

Open the main. CPP file and find the main function

Autoreleasepool __AtAutoreleasePool __autoreleasepool; This line of code.

Find __AtAutoreleasePool in the main. CPP file

__AtAutoreleasePool is a structure, Internally a constructor objc_autoreleasePoolPush() creates an automatic releasepool object atAutoReleasepoolobj and a destructor objc_autoreleasePoolPop(atAutoReleasepoolobj) ).

The automatic releasepool is used to contain the code of the scope in the __AtAutoreleasePool constructor and destructor, and the atAutoReleasepoolobj object manages the scope’s memory.

We can also verify this by looking at the assembly code

Autoreleasepool source code exploration

Open a copy of objC4-781’s source code and search globally for objc_autoreleasePoolPush to find its implementation

The underlying call AutoreleasePoolPage: : push ()

Autoreleasepool data structure

We continue to follow up the source code, checkAutoreleasePoolPageData structure of

AutoreleasePoolPage inherits from AutoreleasePoolPageData

  • magic: verifies the integrity of the AutoreleasePoolPage structure.
  • next: points to the next location of the newly added Autoreleased object, initialized to begin;
  • thread: points to the current thread;
  • parent: refers to the parent. The parent of the first AutoreleasePoolPage node is nil.
  • child: points to a child. The last AutoreleasePoolPage child is nil.
  • depth: The depth of the current node, starting from 0 and increasing later;
  • hiwat: represents hige water mark mark of the maximum number of stacks;

Autoreleasepool is a bidirectional linked list managed by AutoreleasePoolPage.

Objc_autoreleasePoolPush source code review

Objc_autoreleasePoolPush low-level calls AutoreleasePoolPage: : push (), we’ll take a look at a push () of the source code

  • POOL_BOUNDARY: is a sentinel object whose value is nil;
  • AutoreleaseNewPage: processing under DebugPoolAllocation;
  • AutoreleaseFast: Handling of other cases.

In viewautoreleaseFast()The source of 1.page->add(obj): Processing when the current page exists and is not full

Next points to the newly added obj and next++, so you can concatenate the newly added AutoRelease object with next as a one-way linked list.

2.autoreleaseFullPage(obj, page): Processing of full pages

  • When the page is full, the do while loop finds the last page and creates a new page;
  • setHotPage(page): marks the current page ashotPage;
  • page->add(obj);: Adds obj to the page.

3. AutoreleaseNoPage (obj) : Processing of non-existent pages

If the page does not exist, create a new page first, then set the sentinel object, and finally add obj to the page

AutoreleasePoolPage Memory structure of a page table

Take a look at the source code for creating a new page

The first element of the AutoreleasePoolPage structure is magic, and the value passed here is BEGIN (). Let’s take a look at the content of begin() and interrupt

The breakpoint is in the begin() function, where this is the AutoreleasePoolPage structure object. The console prints the address of this pointer and the size of the structure. At least one object must be created inside @Autoreleasepoo {} to break the breakpoint.

AutoreleasePoolPageThe memory size of each element is as follows

This gives you 40 space for all elements other than Magic, and let’s take a look at magic_t

This explains why AutoreleasePoolPage is initialized with 56 bytes shifted to hold the basic structure of the page.

Print the structure of autoReleasepool with the following code

  • print-out0x103009000Is the starting address of the page table;
  • 0x103009038Is the address of the sentinel object;
  • Next is the address pointed to by the object added to the auto-release pool.

Change the number of times for in the code to 505 and print the following

At this time, it is found that 504 objects are placed in a new page, indicating that the number of pages stored on the first page is 504.

Let’s see how many objects we can put on page two

This shows that the page after the first page can hold 505 objects

Why is the first page 504 objects? Why are there 505 objects per page starting from page 2

Let’s look at the function that determines a full page

We follow the end() function and find a constant defined by the macro –4096

This gives us a memory size of 4096 bytes per page

From the above, we can get the following formula:

  • First page size:504 * 8 + 8 + 56 = 4096; The first page starts with 56 bytes to store the basic data of the page, 8 bytes to store the sentinel, and then it can hold 504 objects, with each object pointer taking 8 bytes;
  • Start on page 2:505 * 8 + 56 = 4096; The 56 bytes store the basic data of the page, followed by 505 objects, with each object pointer accounting for 8 bytes;

Finally, the bidirectional linked list structure is obtained as shown in the figure below

In fact, we can already see from the memory structure of the printed page that the sentinel only prints on the first page, and indeed, there is only one sentinel. This sentry acts as a boundary when the autorelease pool is released, and when the sentry is encountered, the release stops.

Objc_autoreleasePoolPop source code erase

AutoreleasePoolPage:: POP (CTXT);

  • When released, set hotPage to nil; Set the current page to coldPage; Token points to begin() on the current page; Record the start and end locations of the memory to be freed. Call the popPage function.

  • releaseUntil: Release objects;
  • page->kill(): Destroys the current page;
  • setHotPage(parent): Sets the previous page of the current page to hotPage.
  • If the child of the current page exists, kill it too.

conclusion

Autorelease is a bidirectional linked list based on Autorelease EpoolPage. The data structure of the page is AutoreleasePoolPageData; Each Autoreleasepool object has only one sentinel, which is placed on the first page; Each page is 4096 bytes in size; The first 56 bytes of each page store the page’s AutoreleasePoolPageData structure. The first page 56 stores sentinels in the next 8 bytes, followed by autoRelease objects, for a total of 504; Starting from the second page, each page can store 505 objects; Objc_autoreleasepoolpush is a process of finding child, incrementing Next, and creating a new page; Objc_autoreleasepoolpop is a process of finding parent, decrement next, releasing objects, destroying pages, and stopping at sentry objects.