preface

When running an iOS (OSX) program, you can see the memory currently in use in the Debug Navigator on the left. We can also use the Allocations template for Instruments to track object creation and release. I wonder if you’ve also been confused by memory mismatch at Debug Navigator and total memory Allocations. This article will take you deep into iOS memory allocation.

Allocations template

In the Allocations templates for Instruments, you see that the main statistic is memory usage of the All Heap & Anonymous VM. The All Heap is the amount of memory allocated on the Heap during App execution. We can search by keyword to see how much memory is allocated on the heap for the class you care about. So what is Anonymous VM? According to the official description, it is the VM regions associated with your App process. The original text reads as follows.

interesting VM regions such as graphics- and Core Data-related. Hides mapped files, dylibs, and some large reserved VM regions.
Copy the code

Introduction to Virtual Memory

What are VM Regions? The first step to understanding this is to understand what virtual memory is. When we request memory from the system, the system does not return the address of physical memory, but gives you a virtual memory address. Each process has the same amount of virtual address space, which can be up to 4GB of virtual memory for a 32-bit process and up to 18EB for a 64-bit process. Only when we start using the requested virtual memory will the system map the virtual address to the physical address, allowing the program to use the real physical memory. Here’s a schematic. I’ve simplified the concept.

Processes A and B both have 1 to 4 virtual memory. The system maps virtual memory to physical memory so that both A and B can use the physical memory. In the figure above, physical memory is sufficient, but what if A uses most of it and B wants to use physical memory but doesn’t have enough? On OSX, the system writes inactive memory blocks to the hard drive, generally referred to as considerations out. On iOS, the App is notified to clear Memory, also known as a Memory Warning.

paging

The system pages virtual memory and physical memory, and the mapping from virtual memory to physical memory is paged to a minimum granularity. In OSX and early iOS, both physical and virtual memory were paged at 4KB. Recent iOS systems, based on A7 and A8 processors, have 4KB physical memory pages and 16KB virtual memory pages. On A9 processor-based systems, both physical and virtual memory are paged at 16KB. The system divides the memory pages into three states.

  1. Active Pages – This memory page has been mapped to physical memory, has been accessed recently, and is active.
  2. Inactive pages – This memory page has been mapped to physical memory, but has not been accessed recently.
  3. Free Pages – Collection of physical memory pages that are not associated with virtual memory pages.

When the number of available pages drops below a certain threshold, the system takes a low Memory response. In OSX, the system swaps inactive pages to hard disk, while in iOS, a Memory Warning is triggered. If your App does not handle the low Memory Warning and still uses too much Memory in the background, it may be killed.

VM Region

To better manage memory pages, the system associates a set of contiguous memory pages with a VMObject that contains the following properties.

  • Resident Pages – List of virtual memory pages that have been mapped to physical memory
  • Size – The Size of the area occupied by all memory pages
  • Pager – Handles the swapping of memory pages between hard disk and physical memory
  • Attributes – Attributes for this memory area, such as access control for reads and writes
  • Shadow – Used for copy-on-write copy optimization
  • Copy – Used as a copy-on-write Copy optimization for our InstrumentsAnonymous VMEvery record you see in VMObject is a VMObject, or as you might call itVM Region.

Heap and VM regions

So what is the relationship between heap and VM Region? The heap should also be a VM Region. How do we know how the heap relates to the VM Region? There is a VM Track template in Instruments to help us understand their relationship. I created an empty Command Line Tool App.

Use the following code.

int main(int argc, const char * argv[]) {
    NSMutableSet *objs = [NSMutableSet new];
    @autoreleasepool {
        for (int i = 0; i < 1000; ++i) {
            CustomObject *obj = [CustomObject new];
            [objs addObject:obj];
        }
        sleep(100000);
    }
    return 0;
}
Copy the code

CustomObject is a simple OC class that contains only one array property of type LONG.

@interface CustomObject() {
    long a[200];
}
@end
Copy the code

Run the Profile, select the Allocation template, and then add the VM Track template.

As you can see under All Heap & Anonymous VM, CustomObject has 1000 instances. Click the arrow to the right of CustomObject to see the object address.

The first address is 0x7FAAB2800000. We switched to VM Track at the bottom and changed the mode to Regions Map.

Then we find the Region whose Address Range starts with 0x7FAAB2800000. We find that the Region Type is MALLOC_SMALL. Click the arrow to see the details, and you will see a list of memory pages in the Region.

As you may have noticed, the memory page in the screenshot is marked under the Swapped column, because I was testing the App on the Mac, so when the memory page is inactive it will be Swapped to the hard disk. This verifies the exchange mechanism we mentioned above. If we make the size of the CustomObject larger, such as the following.

@interface CustomObject() {
    long a[20000];
}
@end
Copy the code

What happens to memory? The answer is that the CustomObject will be moved to the MALLOC_LARGE memory area.

So in general, the heap is divided into many different VM regions, and different types of memory allocation go into different VM regions based on demand. In addition to MALLOC_LARGE and MALLOC_SMALL, there are also MALLOC_TINY, MALLOC metadata, and so on. I’m still figuring out exactly what memory goes into what VM Region.

VM Region Size

We can see from VM Track that a VM Region has four sizes.

  • Dirty Size
  • Swapped Size
  • Resident Size
  • Virtual Size Virtual SizeAs the name implies, this is the virtual memory size, which is the value of subtracting the start address from the end address of a VM Region.Resident SizeThis refers to the amount of physical memory actually used.Swapped SizeIs the size swapped to hard disk, available only to OSX.Dirty SizeA memory page is Dirty if it must be written to hard disk in order to be reused. Here’s the official answerDirty SizeThe explanation.secondary storageYou can view it as a hard disk.
The amount of memory currently being used that must be written to secondary storage before being reused.
Copy the code

Therefore, in general, pages dynamically allocated on the heap during app running are Dirty, while pages generated by loading dynamic libraries or file memory maps are non-dirty. Resident Size + Swapped Size >= Dirty Size + Swapped Size

Malloc and calloc

In addition to using NSObject’s alloc to allocate memory, we can also use c’s function malloc to allocate memory. Malloc allocates virtual memory first and then maps it to physical memory as it is used, but malloc has a defect that requires memset to set all values in the memory area to 0. This leads to the problem that when Malloc allocates a memory region, the system does not allocate physical memory. However, after calling memset, the system will associate all virtual memory from malloc with physical memory, because you have access to all memory regions. Let’s verify that with the code. In the main method, create a memory block of 1024 x 1024, that is, 1M.

void *memBlock = malloc(1024 * 1024);
Copy the code

We found a VM Region with a virtual memory size of 1M in MALLOC_LARGE. Because we’re not using this block of memory, all the other sizes are 0. Now let’s add memset and look.

void *memBlock = malloc(1024 * 1024);
memset(memBlock, 0, 1024 * 1024);
Copy the code

Now the Resident Size is 1 MB and the Dirty Size is 1 MB, indicating that the memory has been mapped to the physical memory. To solve this problem, Apple officially recommends using Calloc instead of Malloc. The memory region returned by Calloc is automatically zeroed out and only associated with physical memory when used.

Malloc_zone_t and NSZone

I’m sure you’re familiar with NSZone, allocWithZone or copyWithZone. So what exactly is a Zone? A Zone can be regarded as a group of memory blocks. Memory blocks allocated in a Zone will be destroyed as the Zone is destroyed. Therefore, a Zone can speed up the collective destruction of a large number of small memory blocks. But NSZone has been effectively abandoned by Apple. You can create your own NSZone and assign your OC objects to that NSZone using allocWithZone, but your objects will still be assigned to the default NSZone. We can use the heap tool to see the Zone distribution of the process. First use the following code to make the CustomObject use the new NSZone.

void allocCustomObjectsWithCustomNSZone() {
    static NSMutableSet *objs = nil;
    if (objs == nil) { objs = [NSMutableSet new]; }
    
    NSZone *customZone = NSCreateZone(1024, 1024, YES);
    NSSetZoneName(customZone, @"Custom Object Zone");
    for(int i = 0; i < 1000; ++i) { CustomObject *obj = [CustomObject allocWithZone:customZone]; [objs addObject:obj]; }}Copy the code

The code creates 1000 CustomObject objects and tries to use the newly created Zone. Let’s use the heap tool to see the results. First use the Activity Monitor to find the PID of the process and execute it on the command line

heap PID
Copy the code

The results of the implementation are roughly as follows.

. Process 25073: 3 zones Zone DefaultMallocZone_0x1004c9000: Overall size: 196992KB; 13993 nodes mallocedfor 160779KB (81% of capacity); largest unused: [0x102800000-171072KB]
Zone Custom Object Zone_0x1004fe000: Overall size: 1024KB; 1 nodes malloced for1KB (0% of capacity); largest unused: [0x102200000-1024KB] Zone GFXMallocZone_0x1004d8000: Overall size: 0KB All zones: 13994 nodes malloced - 160779KB Zone DefaultMallocZone_0x1004c9000: 13993 nodes - Sizes: 160KB[1000] 64.5KB[1] 16.5KB[1] 13.5KB[1] 4.5KB[3] 2KB[3] 1.5KB[12] 1KB[1] 704[1] 576[13] 528[4] 512[2] 480[1] 464[1] 448[2] 432[1] 400[1] 384[2] 368[1] 352[1] 336[2] 320[1] 272[8] 256[1] 240[4] 208[10] 192[5] 176[3] 160[5] 144[28] 128[48] 112[43] 96[83] 80[519] 64[3044] 48[5415] 32[3640] 16[82] Zone Custom Object Zone_0x1004fe000: 1 nodes - Sizes: 32[1] Zone GFXMallocZone_0x1004d8000: 0 nodes All zones: 13994 nodes malloced - Sizes: 160KB[1000] 64.5KB[1] 16.5KB[1] 13.5KB[1] 4.5KB[3] 2KB[3] 1.5KB[12] 1KB[1] 704[1] 576[13] 528[4] 512[2] 480[1] 464[1] 448[2] 432[1] 400[1] 384[2] 368[1] 352[1] 336[2] 320[1] 272[8] 256[1] 240[4] 208[10] 192[5] 176[3] 160[5] 144[28] 128[48] 112[43] 96[83] 80[519] 64[3044] 48[5415] 32[3641] 16[82] Found 523 ObjC classes Found 56 CFTypes ----------------------------------------------------------------------- Zone DefaultMallocZone_0x1004c9000: 13993 nodes (164637440 bytes) COUNT BYTES AVG CLASS_NAME TYPE BINARY ===== ===== === ========== ==== ====== 12771 779136 61.0 Non-object 1000 163840000 163840.0 CustomObject ObjC VMResearch 49 2864 58.4 CFString ObjC CoreFoundation 21 1344 64.0 pthread_mutex_t C libpthread.dylib 20 1280 64.0 CFDictionary ObjC CoreFoundation 18 2368 131.6 CFDictionary (Value Storage) C CoreFoundation 16 2304 144.0 CFDictionary (Key Storage) C CoreFoundation 8 512 64.0 CFBasicHash CFType CoreFoundation 7 560 80.0 CFArray ObjC CoreFoundation 6 768 128.0 CFPrefsPlistSource ObjC CoreFoundation 6 480 80.0 OS_os_log ObjC libsystem_trace.dylib 5 160 32.0 NSMergePolicy ObjC CoreData 4 384 96.0 NSLock ObjC Foundation...... ----------------------------------------------------------------------- Zone Custom Object Zone_0x1004fe000: 1 nodes (32 bytes) COUNT bytes AVG CLASS_NAME TYPE BINARY = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 1 and 32.0 non - object ----------------------------------------------------------------------- Zone GFXMallocZone_0x1004d8000: 0 nodes (0 bytes)Copy the code

Zone Custom Object Zone_0x1004fe000: 1 nodes (32 bytes) is the NSZone we created, but there is only one node in it. The total number of bytes is 32. If you do not set the Zone name, it will be 0bytes. So we can deduce that the 32bytes are used to store information about the Zone itself. The 1000 CustomObjects we created are actually in Zone DefaultMallocZone_0x1004c9000, which is the NSZone created by default. If you really want to use the Zone memory mechanism, you can use malloc_zone_t. You can use the following code to create a malloc memory block on a custom zone.

void allocCustomObjectsWithCustomMallocZone() {
    malloc_zone_t *customZone = malloc_create_zone(1024, 0);
    malloc_set_zone_name(customZone, "custom malloc zone");
    for(int i = 0; i < 1000; ++i) { malloc_zone_malloc(customZone, 300 * 4096); }}Copy the code

Use the heap tool again to see. I just captured the contents of the Custom Malloc zone. There are 1001 nodes, 1000 memory blocks malloc_zone_malloc plus information about the zone itself.

----------------------------------------------------------------------- Zone custom malloc zone_0x1004fe000: 1001 nodes (1228800032 bytes) COUNT BYTES AVG CLASS_NAME TYPE BINARY ===== ===== === ========== ==== ====== 1001 1228800032 1227572.4 non - objectCopy the code

We can use malloc_Destroy_zone (customZone) to free all of the above allocated memory at once.

conclusion

This paper mainly introduces the relevant principles of VM in iOS (OSX) system and how to use VM Track template to analyze VM Regions. This paper only focuses on several VM Regions related to MALLOC and some other dedicated VM Regions. By studying their memory allocation, Memory optimization can be targeted, and that’s what happens next.