Memory layout

  • The stack area【 Usually starts with 0x7 】
    • function
    • Pointer to the
    • Local variables, method parameters
  • The heap area【 Usually starts with 0x6 】
    • An object that allocates memory by alloc
    • block copy
  • Uninitialized data.bss【 Usually starts with 0x1 】
    • An uninitialized global variable
    • An uninitialized static variable
  • Data. The data【 Usually starts with 0x1 】
    • Initialized global variable
    • Initialized static variable
  • Code. The text
    • Program code, loaded into memory

Why is stack access faster than heap access? Because the memory in the stack is located through the SP register, and the heap address needs to be located through the register to the address containing the memory space, and then through this address to the specific location.

Memory management scheme

TaggedPointer

basis

  • Dedicated to storing small objects (NSNumber.NSDate…).
  • The value of a pointer is no longer an address, but a real value. So, really, it’s just a normal variable in object skin. So, its memoryIt’s not stored in the heapI don’t need tomallocandfree
  • Three times more efficient at memory reads and 106 times faster at creation

NONPOINTER_ISA

basis

  • Non-pointer ISA
  • We are exploringisa_tYou will see that one is stored internallyISA_BITFIELDTake a look at the arm64 architectureISA_BITFIELD
#define ISA_BITFIELD       
uintptr_t nonpointer        : 1;    \
uintptr_t has_assoc         : 1;    \        
uintptr_t has_cxx_dtor      : 1;    \      
uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic             : 6;    \    
uintptr_t weakly_referenced : 1;    \    
uintptr_t unused            : 1;    \   
uintptr_t has_sidetable_rc  : 1;    \   
uintptr_t extra_rc          : 19
Copy the code
  1. nonpointer: Indicates whether to enable pointer optimization for isa Pointers. 0: pure ISA pointer, 1: not only class object address, ISA contains class information, object reference count.
  2. has_assoc: Indicates the flag of the associated object. 0: no, 1: exist
  3. has_cxx_dtorDoes the object have a destructor for C++/Objc? If it has a destructor, then the destructor logic needs to be done. If not, the object can be freed faster
  4. shiftcls: Stores the value of the class pointer. With pointer optimization turned on, 33 bits are used to store class Pointers in the ARM64 architecture.
  5. magic: used by the debugger to determine whether the current object is a real object or has no space to initialize
  6. weak_referenced: indicates whether an object is or has been referred to an ARC weak variable. Objects without weak references can be released faster
  7. has_sidetable_rc: When the reference count of an object is greater than 10, the variable is borrowed to store the carry
  8. extra_rcFor example, if the reference count of the object is 10, thenextra_rcIs 9. If the reference count is greater than 10, use the one abovehas_sidetable_rc: When the reference count of an object is greater than 10, you need to borrow this variable to store carry

Hash table SideTable

basis

Hash table hash table hash table hash table hash table hash table hash table hash table hash table

class StripedMap {
#ifTARGET_OS_IPHONE && ! TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif...}Copy the code

According to the StripedMap definition, there are 8 Sidetables on iPhone and 64 sidetables on other platforms

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;  // Reference count
    weak_table_t weak_table;  // 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

MRC & ARC

Retain Process Exploration

When we create an object, we can see from the breakpoint that we go to the _objc_rootRetain method

// _objc_rootRetain
id _objc_rootRetain(id obj) {
    ASSERT(obj);
    return obj->rootRetain(a); }// rootRetain
id objc_object::rootRetain(a) {
    return rootRetain(false, RRVariant::Fast);
}

// Call the overloaded rootRetain method with false and Fast
id objc_object::rootRetain(bool tryRetain, objc_object::RRVariant variant) {···}
Copy the code
  1. Whether it isTaggedPointerIf so, return itself directly (no reference counting), if not, continue.
if (slowpath(isTaggedPointer())) return (id)this;
Copy the code
  1. Whether it isnonpointerType, if isnonpointer
    • Determines whether a retain operation is undergoing demolition or not
    • extra_rcIf the value + 1carryrepresentsnewisa.bitsOverflow occurs when storage is full
/ / operation
uintptr_t carry; // carry indicates whether overflow occurs
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
// RC_ONE
#   define RC_ONE   (1ULL<<56)
Copy the code

If it overflows and the passed argument is not RRVariant::Full, go to the rootRetain_overflow method and return.

if(variant ! = RRVariant::Full) {ClearExclusive(&isa.bits);
    return rootRetain_overflow(tryRetain);
}

// You can see that rootRetain is still called internally, but passed in as RRVariant::Full
id objc_object::rootRetain_overflow(bool tryRetain) {
    return rootRetain(tryRetain, RRVariant::Full);
}
Copy the code

If the parameter is not RRVariant::Full, only half of newisa.extra_rc is retained

// Set the hash table flag to true
sideTableLocked = true;
transcribeToSideTable = true;
// The extra_rc is left in half and has_sideTABLE_rc is set to true for that object
newisa.extra_rc = RC_HALF; // # define RC_HALF (1ULL<<18)
newisa.has_sidetable_rc = true;
Copy the code
  1. If not fornonpointerType, go tosidetable_retain
return sidetable_retain(sideTableLocked) // false
Copy the code

I drew a simple flow chart

weak

When we decorate a variable with __weak, the breakpoint run will see the objc_initWeak method arrive.

id objc_initWeak(id *location, id newObj) {
    if(! newObj) { *location = nil;return nil;
    }
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
Copy the code

Let’s explore the storeWeak method.

  1. According to incomingnewObjGet hash table
newTable = &SideTables()[newObj];
Copy the code

2. Create a weak reference table

weak_register_no_lock(&newTable->weak_table,
                      (id)newObj,
                      location,
                      crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
Copy the code

Focus on the following lines in the Weak_register_NO_lock method

// Address of the weakly referenced object
objc_object *referent = (objc_object *)referent_id;
// Address of the weakly referenced object pointer
objc_object **referrer = (objc_object **)referrer_id;

/ / create weak_entry_t
weak_entry_t new_entry(referent, referrer);
// Determine whether capacity expansion is required
weak_grow_maybe(weak_table);
// Add the new Weak_entry_t to the weak reference table
weak_entry_insert(weak_table, &new_entry);
Copy the code

Weak_entry_t structure

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;
    union {...// Since it is the union bitfield structure, we only focus on the following part for the moment
        struct {
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    };
    // Initialize method
    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) { / / WEAK_INLINE_COUNT of 4inline_referrers[i] = nil; }}}Copy the code