Memory layout – five areas
-
Stack area 0x7 The storage area where temporary variables are automatically allocated by the compiler when they 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.
-
The heap 0x6 blocks allocated by objects created by new alloc are not actively allocated by the system. It is up to our developers to tell the system when to free this block of memory (an object reference count of 0 is destroyed by the system). 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.
-
Static area (uninitialized data). BSS the data in the memory of the program is always there and released by the system after the program ends
-
Constant area (initialized data). Data is specially used to store constants, released by the system after the end of the program
-
Code area a program code area used to hold code that is compiled into binary and stored in memory while the program is running
Memory management scheme
TaggedPointer
Usually we create an object, the object is stored in the heap, the pointer to the object is stored in the stack, and if we want to find the object, we need to find the address of the pointer in the stack, and then we need to find the object in the heap based on the address of the pointer. This process can be tedious when the object is just a small thing, like a string, a number. To go through such a tedious process would cost performance, so Apple came up with a TaggedPointer.
-
TaggedPointer is a memory optimization solution for NSNumber, NSDate, and part of NSString to solve memory usage and efficiency problems caused by the transition 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.
TaggedPointer is explained in more detail here
The interview questions
Why did the second for crash?
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0 ; i<1000000; i++) {
self.str = @"abcd"; }}); dispatch_async(queue, ^{for (int i = 0 ; i<1000000; i++) {
self.str = [NSString stringWithFormat:@"adfalkdjfldkasjflakjsdkflasf-- %d",I]; }});Copy the code
A: taggedpointer. In the setProperty function, objc_release(id obj) is executed. Because of the large number of loops, threading problems were caused, making the reference count <=-1. But since obj in the first loop is a String of type TaggedPointer, it will return obj directly and not release it. But what happens when you release retain, does the reference count keep going up? In objc_retain(id obj), obj->isTaggedPointer is also checked. If true, return obj.
NONPOINTER_ISA
To say ISA, start with the object.
NSObject inheritance:
NSObject -> Class -> objc_class -> objc_object -> isa_t
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
Copy the code
Isa_t isa union, and then focusing on ISA_BITFIELD,
Parameter Description:
Nonpointer: indicates whether pointer optimization is enabled for isa Pointers. 0: indicates pure ISA Pointers. 1: indicates not only the address of the class object, but also the class information and reference count of the object.
Has_assoc: flag bit of the associated object. 0 does not exist and 1 exists.
Has_cxx_dtor: does the object have a destructor for C++ or Objc? If so, the destructor logic needs to be done. If not, the object can be freed more quickly.
Shiftcls: Stores the value of the class pointer. With pointer optimization turned on, there are 33 bits of value in the ARM64 architecture used to store class Pointers.
Magic: Used by the debugger to determine whether the current object is a real object or has no space to initialize.
Weakly_referenced: Indicates whether an object is pointed to or has been pointed to a weak variable of an ARC. Objects without weak references can be released faster.
Deallocating: Indicates whether the object is freeing memory.
Has_sidetable_rc: Useful for hash tables that need to be borrowed to store carry when the object reference count is greater than 10.
Extra_rc: When representing the referential count value of this object, it is actually the referential count value minus 1. For example, if the object’s reference count is 10, extra_rc is 9. Example: In __x86_64(MAC)__ architecture, overflow will occur if the reference count is greater than 255. To overflow, you need to mark has_sideTABLE_rc as 1, which will take **2 to the 7th power (128)** into the hash table (sidetable).
So how does has_sidetable_rc work?
SideTables hash
SideTables
SideTables is an array that holds a number of SideTables. *** This method is called when the object reference count overflows, saving half of the reference count into the sideTable.
SideTable& table = SideTables()[this];
Copy the code
So the thing to look at here is SideTables()
SideTables() is a StripedMap, so look at the StripedMap
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && ! TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endifstruct PaddedT { T value alignas(CacheLineSize); }; PaddedT array[StripeCount]; // Reinterpret_cast; // Reinterpret_cast; // Reinterpret_cast; // Reinterpret_cast Uintptr_t addr = reinterpret_cast<uintptr_t>(p); // Here StripeCount is 64, look at line 755 abovereturn((addr >> 4) ^ (addr >> 9)) % StripeCount; } public: // override the parentheses, c++ only // to support the operands in "[]" const void T& operator[] (const void *p) {// call indexForPointer() to get the sidetablereturn array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
returnconst_cast<StripedMap<T>>(this)[p]; }... }Copy the code
After reading the above code + comments, let’s go through a wave of LLDB debugging and print each parameter separately
Here’s how to get the SideTable
<1> SideTable& table = SideTables()[this]; <3> Returns a SideTable from array[indexForPointer(p)].value
##### primary discussion SideTable spinlock_t: spinlock, RefcountMap: reference countMap, a C++ Map
Weak_table_t: global weak reference table
#####SideTable: sidetable_addExtraRC_nolock
bool objc_object::sidetable_addExtraRC_nolock(size_t delta_rc) { assert(isa.nonpointer); SideTables() SideTables & table = SideTables()[this]; // Obtain reference count size size_t& refcntStorage = table.refcnts[this]; // assign to oldRefcnt size_t oldRefcnt = refcntStorage; // isa-side bits should not besethere assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0); assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0); // if oldRefcnt & SIDE_TABLE_RC_PINNED = 1 // oldRefcnt = 2147483648 (32-bit case)if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true; // Uintptr_t carry; // Reference count add //delta_rc move two bits left, The two on the right are DEALLOCATING(destruction ing) and WEAKLY_REFERENCED(weak reference counting) size_t newRefcnt = ADDC (oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry); // If sideTable overflows as well. / / here I amforMillions of times, without overrunning, you can see that sideTable can hold a lot of reference countsif(carry) {// if it is 32 bits, SIDE_TABLE_RC_PINNED = 1<< (32-1) // the maximum value of int SIDE_TABLE_RC_PINNED = 2147483648 // SIDE_TABLE_FLAG_MASK = 3 / / refcntStorage = 2147483648 | (oldRefcnt & 3) / / if the overflow, The refcntStorage directly to set up the maximum refcntStorage = SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);return true;
}
else {
refcntStorage = newRefcnt;
return false; }}Copy the code
Above, to be continued ~