Memory layout

There are seven areas in the memory layout, namely kernel area, heap area, stack area, uninitialized data (static area), initialized data (constant area), code segment, and retention area.

  • Stack area: An area of storage that is automatically allocated by the compiler when temporary variables are created and cleared when they are no longer needed. The variables inside are usually local variables, function parameters, etc. At the top of the user’s virtual address space in a process is the user stack, which the compiler uses to make function calls. Like the heap, the user stack can expand and contract dynamically during program execution.
  • Heap: Blocks of memory allocated by objects created by new alloc are left to our developers to tell the system when to free them (an object with a zero reference count is destroyed). Usually a new corresponds to a release. Under ARC the compiler automatically adds a release operation to the OC object in the appropriate place. These objects are destroyed when the current thread Runloop exits or sleeps, whereas MRC requires the programmer to release them manually. The heap can expand and contract dynamically.
  • Uninitialized data (static area) : Data stored in the memory of a program during its execution remains and is released by the system after the program ends
  • Initialized data (constant area) : used to store constants and released by the system at the end of the program
  • Code snippet: An area of program code used to hold code that is compiled into binary and stored in memory while a program is running
  • Kernel area: used for loading kernel code, reserved 1GB
  • Retention: the memory is reserved for 4MB, and the addresses increase from low to high

Here’s a code to explore where data is stored for everyday use

  • The stack area:Pointer to the, and someSimple basic data typesStored in theThe stack area. In general, the address is0 x7 beginningAre generally inThe stack area
#import "ViewController.h"
@interface ViewController ()
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"* * * * * * * * * * * * stack area * * * * * * * * * * * *"); Int a = 10; int b = 20; NSObject *object = [NSObject new]; NSLog(@"a == \t%p",&a);
    NSLog(@"b == \t%p",&b);
    NSLog(@"object == \t%p",&object);
    NSLog(@"%lu",sizeof(&object));
    NSLog(@"%lu",sizeof(a));
}
@end
Copy the code

Print:

The 2020-02-13 16:33:11. 605082 + 0800 001 - five regional Demo [1545, 519291] * * * * * * * * * * * * stack area of * * * * * * * * * * * * 2020-02-13 16:33:11. 605169 + 0800 001-- Demo[1545:519291] A == 0x7FFEE0cd87fc2020-02-13 16:33:11.605236+0800 001-- 5 large area Demo[1545:519291] b == 0x7FFEE0cd87F8 2020-02-13 16:33:11.605298+0800 001-- Demo[1545:519291] object == 0x7FFEE0cd87F0 2020-02-13 16:33:11.605357+0800 001-- Demo[1545:519291] 8 2020-02-13 16:33:11.605411+0800 001-- Demo[1545:519291] 4 of five major regionsCopy the code
  • The heap area:The heap areaGenerally used for storageThe object that comes out of newGenerally speaking,The heap areaThe address of is generally0 x6 beginning
#import "ViewController.h"
@interface ViewController ()
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"* * * * * * * * * * * * heap area * * * * * * * * * * * *"); NSObject *object1 = [NSObject new]; NSObject *object2 = [NSObject new]; NSObject *object3 = [NSObject new]; NSObject *object4 = [NSObject new]; NSObject *object5 = [NSObject new]; NSObject *object6 = [NSObject new]; NSObject *object7 = [NSObject new]; NSObject *object8 = [NSObject new]; NSObject *object9 = [NSObject new]; NSLog(@"object1 = %@",object1);
    NSLog(@"object2 = %@",object2);
    NSLog(@"object3 = %@",object3);
    NSLog(@"object4 = %@",object4);
    NSLog(@"object5 = %@",object5);
    NSLog(@"object6 = %@",object6);
    NSLog(@"object7 = %@",object7);
    NSLog(@"object8 = %@",object8);
    NSLog(@"object9 = %@",object9);
}
@end
Copy the code

Print:

The 2020-02-13 16:33:11. 605468 + 0800 001 - five regional Demo [1545, 519291] * * * * * * * * * * * * heap area * * * * * * * * * * * * 2020-02-13 16:33:11. 605531 + 0800 001-- Demo[1545:519291] object1 = <NSObject: 0x600003854440> 2020-02-13 16:33:11.605601+0800 001-- Object2 = <NSObject: 0x600003854460> 2020-02-13 16:33:11.605655+0800 001-- Object3 = <NSObject: 0x600003854450> 2020-02-13 16:33:11.605720+0800 001-- Demo[1545.519291] object4 = <NSObject: 0x600003854470> 2020-02-13 16:33:11.605776+0800 001-- Object5 = <NSObject: 0x600003854470> 2020-02-13 16:33:11.605776+0800 001 0x600003854480> 2020-02-13 16:33:11.605840+0800 001-- Demo[1545.519291] object6 = <NSObject: 0x600003854490> 2020-02-13 16:33:11.605902+0800 001-- Object7 = <NSObject: 0x600003854490> 2020-02-13 16:33:11.605902+0800 001 0x6000038544A0 > 2020-02-13 16:33:11.605965+0800 001-- Object8 = <NSObject: 0x6000038544B0 > 2020-02-13 16:33:11.606028+0800 001-- Demo[1545.519291] object9 = <NSObject: 0x6000038544C0 >Copy the code
  • Static area: Stores objects that are defined but not initialized. In general,0 x1 beginningThe data of is generallyThe constant areaandStatic area
#import "ViewController.h"
@interface ViewController ()

@end

@implementation ViewController

int clA;
static int bssA;
static NSString *bssStr1;

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"************ static zone ************");
    NSLog(@"clA == \t%p",&clA);
    NSLog(@"bssA == \t%p",&bssA);
    NSLog(@"bssStr1 == \t%p",&bssStr1);
   
}
@end
Copy the code

Print:

The 2020-02-13 16:33:11. 623869 + 0800 001 - five regional Demo [1545, 519291] * * * * * * * * * * * * static area * * * * * * * * * * * * 2020-02-13 16:33:11. 623950 + 0800 624016+0800 001-- Demo[1545.519291] bssA == 0x10EF272A0 2020-02-13 16:33:11.624067+0800 001-- 5 large area Demo[1545:519291] bssStr1 == 0x10EF272a8Copy the code
  • The constant area: stores some defined and initialized data. In general,0 x1 beginningThe data of is generallyThe constant areaandStatic area
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

int clB = 10;
static int bssB = 10;
static NSString *bssStr2 = @"noah";

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"************ Constant area ************");
    NSLog(@"clB == \t%p",&clB);
    NSLog(@"bssB == \t%p",&bssB);
    NSLog(@"bssStr2 == \t%p",&bssStr2);
   
}
@end
Copy the code

Print:

The 2020-02-13 16:33:11. 624130 + 0800 001 - five regional Demo [1545, 519291] * * * * * * * * * * * * constants area * * * * * * * * * * * * 2020-02-13 16:33:11. 624192 + 0800 520-02-13 16:33:11.624244+0800 001-- 55-region Demo[1545.519291] bssB == 0x10EF271d0 2020-02-13 16:33:11.624300+0800 001-- Demo[1545:519291] bssStr2 == 0x10EF271C8Copy the code

Memory Management Policy

TaggedPointer

Why use taggedPointer? Suppose you want to store an NSNumber object whose value is an integer. Normally, if this integer is just an ordinary variable of NSInteger, it will take up to 8 bytes on a 64-bit CPU. A byte has eight bits. If we store a very small value, many bits will be zeros, which causes memory waste. To solve this problem, Apple introduced the concept of taggedPointer.

  • Tagged Pointer is a memory optimization solution for NSNumber, NSDate, and some NSStrings to solve memory usage and efficiency problems caused by the switch from 32-bit CPU to 64-bit CPU.
  • The Tagged Pointer value is no longer an address, but a real value. So, it’s not really an object anymore, it’s just a normal variable in an object’s skin. Therefore, its memory is not stored in the heap and does not require malloc and free.
  • Tagged Pointer contains the address, type, and value of the current object. Thus, Tagged Pointer is 3 times more efficient at reading memory and 106 times faster to create than a normal malloc and free type.
  • Click here to learn more about TaggedPointer

TaggedPointer source

// Create static inline void * _Nonnull _objc_makeTaggedPointer(objc_tag_index_T tag, uintptr_t value) {if (tag <= OBJC_TAG_Last60BitPayload) {
        uintptr_t result =
            (_OBJC_TAG_MASK | 
             ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | 
             ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
        return _objc_encodeTaggedPointer(result);
    } else {
        uintptr_t result =
            (_OBJC_TAG_EXT_MASK |
             ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
             ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
        return_objc_encodeTaggedPointer(result); Void * _Nonnull _objc_encodeTaggedPointer(uintptr_t PTR) {return(void *)(objc_debug_taggedpointer_obfuscator ^ ptr); } // Decode static uintptr_t _objc_decodeTaggedPointer(const void * _Nullable PTR) {return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
Copy the code

The system encodes taggedPointer _objc_encodeTaggedPointer. The encoding is implemented by performing the xOR operation of objC_debug_TaggedPOinter_obfuscator on value. When reading a taggedPointer, decode it by _objc_decodeTaggedPointer, or xor of objC_debug_taggedPOinter_obfuscator. After xor operation, the initial value is restored.

Use code to validate the taggedPointer type

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    int     num1 = 10;
    float   num2 = 12;
    double  num3 = 14;
    long    num4 = 5;
    
    NSNumber * number1 = @(num1);
    NSNumber * number2 = @(num2);
    NSNumber * number3 = @(num3);
    NSNumber * number4 = @(num4);
    
    NSLog(@"number1 = %@ - %p - 0x%lx",number1,&number1,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(number1)));
    NSLog(@"number2 = %@ - %p - 0x%lx",number2,&number2,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(number2)));
    NSLog(@"number3 = %@ - %p - 0x%lx",number3,&number3,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(number3)));
    NSLog(@"number4 = %@ - %p - 0x%lx",number4,&number4,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(number4)));
}

extern uintptr_t objc_debug_taggedpointer_obfuscator;

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

@end
Copy the code

print

2020-02-13 19:35:33.310386+0800 004-TagGEdPointer [3472:589831] Number1 = 10-0x7FFEE4bCC7e0-0xB0000000000000A2 2020-02-13 19:35:33.310471+0800 004-TagGEdPointer [3472:589831] Number2 = 12-0x7FFEE4bCC7D8-0xB0000000000000C4 2020-02-13 19:35:33.310529+0800 004-TagGEdPointer [3472:589831] Number3 = 14-0x7FFEE4bCC7d0-0xB0000000000000e5 2020-02-13 19:35:33.310578+0800 004-TagGEdPointer [3472:589831] Number4 = 5-0x7FFEE4bCC7C8-0xB000000000000053Copy the code

For number1, the value 0xB0000000000000A2 decoded from _objc_decodeTaggedPointer is the value, and the last digit is the type. The last digit, 2, 4, 5, and 3, respectively represent the type int long float double

Consider the string type

//
//  ViewController.m
//  004-taggedPointer
//
//  Created by cooci on 2019/4/8.
//  Copyright © 2019 cooci. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    NSString * str1 = [NSString stringWithFormat:@"a"];
    NSString * str2 = [NSString stringWithFormat:@"ab"];
    NSString * str3 = [NSString stringWithFormat:@"abc"];
    NSString * str4 = [NSString stringWithFormat:@"abcd"];
    
    NSLog(@"str1 = %@ - %p - 0x%lx",str1,&str1,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(str1)));
    NSLog(@"str1 = %@ - %p - 0x%lx",str2,&str2,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(str2)));
    NSLog(@"str1 = %@ - %p - 0x%lx",str3,&str3,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(str3)));
    NSLog(@"str1 = %@ - %p - 0x%lx",str4,&str4,_objc_decodeTaggedPointer((__bridge const void * _Nullable)(str4)));
}

extern uintptr_t objc_debug_taggedpointer_obfuscator;

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

@end
Copy the code

Print:

2020-02-13 19:42:22.152170+0800 004-TaggedPointer [3539:593334] str1 = A-0x7FFEE3BF27c0-0xA000000000000611 2020-02-13 19:42:22.152257+0800 004-TaggedPointer [3539:593334] str1 = AB-0x7FFee3BF27b8-0xA000000000062612 2020-02-13 19:42:22.152322+0800 004-TaggedPointer [3539:593334] str1 = ABC-0x7FFee3BF27B0-0xA000000006362613 2020-02-13 19:42:22.152386+0800 004-taggedPointer[3539:593334] str1 = ABcd - 0x7FFee3BF27a8-0xA000000646362614Copy the code

The last bit represents the length of the string. 61, 62, 63, and 64 correspond to the ASCII characters A, B, C, and D

NONPOINTER_ISA

Apple has designed ISA to be federated, storing some of the memory information associated with the object in ISA, for the reason that, as mentioned above, not all 64 bits are needed to store Pointers. Take a look at the structure of ISA:

// struct {uintptr_t nonpointer: 1; // uintptr_t has_assoc: 1; Uintptr_t has_cxx_dtor: 1; // Uintptr_t shiftcls: 44; Uintptr_t magic: 6; // Uintptr_t magic: 6; Uintptr_t Weakly_referenced: 1; // Whether the locating is weakly referenced to the uintptr_t deallocating: 1; // Whether the object is releasing the uintptr_t has_sidetable_rc: 1; Uintptr_t extra_rc: 8; // Reference counts can be stored in 8 bits, stored directly here}; // struct {uintptr_t nonpointer: 1; // uintptr_t has_assoc: 1; Uintptr_t has_cxx_dtor: 1; // Uintptr_t shiftcls: 33; Uintptr_t magic: 6; // Uintptr_t magic: 6; Uintptr_t Weakly_referenced: 1; // Whether the locating is weakly referenced to the uintptr_t deallocating: 1; // Whether the object is releasing the uintptr_t has_sidetable_rc: 1; Uintptr_t extra_rc: 19; // Reference counts can be stored in 19 bits, stored directly here};Copy the code

Note that has_sideTABLE_rc and extra_rc are used here. Has_sidetable_rc indicates whether the pointer refers to the SIDETable hash table. This option is available because small reference counts are not stored directly in the SideTables table. The object’s reference count is stored in extra_RC first, and only when it is full is stored in the corresponding SideTables hash. There are many SideTables in SideTables, and each SideTable is also a hash. The reference counting table is included in the SideTable.

SideTables hash

A Hash table is a data structure that accesses data stored in memory based on a Key. That is, it accesses records by mapping the data for the desired query to a location in the table through an about key value function, which speeds up lookups. This mapping function is called a hash function, and the array of records is called a hash table.

Take a look at their corresponding source code in nsobject. mm:

// SideTables
static StripedMap<SideTable>& SideTables() {
    return*reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf); } // SideTable struct SideTable { spinlock_t slock; // spinlock RefcountMap refcnts; Weak_table_t weak_table; // other code... };Copy the code

Their relationship is as follows:

SideTables table = &SideTables()[obj]; // SideTables static StripedMap<SideTable>&SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
Copy the code
// StripedMap template<typename T>#if TARGET_OS_IPHONE && ! TARGET_OS_SIMULATOR
    //如果是真机,StripeCount=8
    enum { StripeCount = 8 };
#elseStripeCount=64 enum {StripeCount=64};#endifstruct PaddedT { T value alignas(CacheLineSize); }; PaddedT array[StripeCount]; // Static unsigned int indexForPointer(const void *p) {uintptr_t addr = reinterpret_cast<uintptr_t>(p); // This algorithm ensures that the calculated value is less than StripeCount, so that no data is out of boundsreturn((addr >> 4) ^ (addr >> 9)) % StripeCount; } public: // Override operator T& operator[] (const void *p) {// return a sideTablereturn array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        returnconst_cast<StripedMap<T>>(this)[p]; }... };Copy the code

Separation of the lock

A split lock is not a lock, but a use of the lock. A separate lock for each element is what we call a separate lock.

Q: Why not use SideTables to include spin locks, reference counting tables, and weak reference tables directly?

A: If the SideTable is accessed by multiple threads at the same time, all data is accessed from the SideTable in a single thread, and all data is accessed from the SideTable in one thread. This leads to a very bad user experience. In this case, a SideTable is divided into multiple SideTables and locked separately to ensure data security. This increases the concurrency and improves the efficiency of data access, which is why there are many SideTables under one SideTables.

Spin lock: A lock used in computer science for multithreaded synchronization in which the thread repeatedly checks whether the lock variable is available. Because the thread keeps executing during this process (it does not go to sleep), it is a busy, etc. Once a spin lock is acquired, the thread holds it until it explicitly releases the spin lock.

Spinlocks are suitable for small data, low time consuming operations, and are fast.

RefcountMap Refcnts

SideTable contains a c++ Map RefcountMap refcnts to store extra reference counts for objects, and a struct weak_table_t weak_table to store weak reference data for objects

RefcountMap The reference count is held by a size_t (64 bits in 64-bit systems), where one bit is used to store the fixed flag bit for overflow, one bit to indicate that the reference is being released, one bit to indicate whether there is a weak reference, and the remaining bits to indicate the actual reference count

  • Refcnts are a C++ object that contains an iterator
  • The DisguisedPtr< objC_object > object pointer is the key, and size_t is the value to save the object reference count
  • After STD ::pair packaging, key and value are put into iterators, so after fetching values,.first represents key and.second represents value

A weak reference table

Global weak reference table Weak_table_t

struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};
Copy the code

The internal structure of weak reference tables

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
};
Copy the code
  • There are two main attributesDisguisedPtr<objc_object> referentObject pointer, and a container class holds it so you only need a weak reference to that object
  • There are two types of structures in the common, when the number of weak references is less4When using data structures to store when over4Is stored in a hash table.out_of_line_nessThe default isob00, when the number of weak references is greater than4“Is set toREFERRERS_OUT_OF_LINE ob10By judgmentout_of_line_nessTo decide how to store it
  • weak_referrer_t *referrersIs a hash table with a second-level pointer implementation

Refer to the article

IOS concept road (3) : Memory management iOS weak implementation