Blog link OC Memory management – reference counter
Storage policy for reference counting
- Some objects are used if supported
TaggedPointer
, Apple returns its pointer value directly as a reference count; - If the current device is
64
Bit environment and useObjective - 2.0 C
“, then “some” object will use itisa
A portion of the pointer’s space to store its reference count; - Otherwise,
Runtime
A hash table is used to manage the reference count.
Tagged Pointer
Tagged Pointer is used to optimize memory.
Tagged Pointer
Designed to store small objects such asNSNumber
andNSDate
And so on;Tagged Pointer
The value of a pointer 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. So, its memory is not stored in the heap and is not neededmalloc
andfree
;- Three times more efficient at memory reads and 106 times faster at creation.
The following implementation is used to reflect Tagged Pointer on 64-bit systems:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSNumber *number1 = @1;
NSNumber *number2 = @2;
NSNumber *number3 = @3;
NSNumber *number4 = @4;
NSNumber *numberLager = @(MAXFLOAT);
NSLog(@"number1 pointer is %p", number1);
NSLog(@"number2 pointer is %p", number2);
NSLog(@"number3 pointer is %p", number3);
NSLog(@"number4 pointer is %p", number4);
NSLog(@"numberLager pointer is %p", numberLager);
returnUIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }} // Print the result: 2018-09-25 15:26:05.788382+0800 NSObjectProject[68029:24580896] Number1 Pointer is 0x9C344c19D780bc93 2018-09-25 15:26:05.789257+0800 NSObjectProject[68029:24580896] Number2 Pointer is 0x9C344c19D780bCA3 2018-09-25 15:26:05.789383+0800 NSObjectProject[68029:24580896] Number3 Pointer is 0x9C344c19D780bCB3 2018-09-25 15:26:05.789489+0800 NSObjectProject[68029:24580896] Number4 Pointer is 0x9C344C19D780bCC3 2018-09-25 15:26:05.789579+0800 NSObjectProject[68029:24580896] numberLager Pointer is 0x600001E60D80Copy the code
As we know, all objects have their corresponding ISA Pointer, so the introduction of Tagged Pointer will affect the ISA Pointer.
Let’s look at the use of Tagged Pointer on an object
inline bool
objc_object::isTaggedPointer() {
return _objc_isTaggedPointer(this);
}
Copy the code
How to determine if it is Tagged Pointer’s object:
- Look at the object. When it comes to in front of the
Tagged Pointer
It’s designed to store small objects that haveNSDate
,NSNumber
,NSString
; - Set your own. Set in environment variables
OBJC_DISABLE_TAGGED_POINTERS
forYES
Indicates forcibly disabledTagged Pointer
.
Isa pointer
The essence of ISA — ISA_T consortium
The isa pointer is defined in the objc_object structure:
struct objc_object { isa_t isa; } // the definition of isa_t union isa_t {isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
// extra_rc must be the MSB-most field (so it matches carry/overflow flags)
// nonpointer must be the LSB (fixme or get rid of it)
// shiftcls must occupy the same bits that a real class pointer would
// bits + RC_ONE is equivalent to extra_rc + 1
// RC_HALF is the high bit of extra_rc (i.e. half of its range)
// future expansion:
// uintptr_t fast_rr : 1; // no r/r overrides
// uintptr_t lock : 2; // lock for atomic property, @synch
// uintptr_t extraBytes : 1; // allocated with extra bytes
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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 deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
# else
# error unknown architecture for packed isa
# endif
// SUPPORT_PACKED_ISA
#endif
#if SUPPORT_INDEXED_ISA
# if __ARM_ARCH_7K__ >= 2
# define ISA_INDEX_IS_NPI 1
# define ISA_INDEX_MASK 0x0001FFFC
# define ISA_INDEX_SHIFT 2
# define ISA_INDEX_BITS 15
# define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS)
# define ISA_INDEX_MAGIC_MASK 0x001E0001
# define ISA_INDEX_MAGIC_VALUE 0x001C0001
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t indexcls : 15;
uintptr_t magic : 4;
uintptr_t has_cxx_dtor : 1;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 7;
# define RC_ONE (1ULL<<25)
# define RC_HALF (1ULL<<6)
};
# else
# error unknown architecture for indexed isa
# endif
// SUPPORT_INDEXED_ISA
#endif
};
Copy the code
There are a number of environments defined here, but let’s focus on the 64-bit CPU (if __arm64__) definition:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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 deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
Copy the code
nonpointer
This variable takes up 1bit of memory and can have two values: 0 and 1, representing different types of ISA_t:
0
saidisa_t
Pointer optimization is not enabled and is not usedisa_t
A structure defined in. accessobjc_object
theisa
It’s going to go straight backisa_t
The structure of thecls
Variables,cls
Variables point to the structure of the class to which the object belongs;1
saidisa_t
Pointer optimization is enabled. Direct access is unavailableobjc_object
theisa
The member variable (isa at this point is oneTagged Pointer
),isa
Contains information about classes and reference counts of objects.
has_assoc
This variable is associated with an associated reference to an object.
has_cxx_dtor
Indicates whether the object has a destructor. If so, the destructor logic needs to be done. If not, the object can be released more quickly.
shiftcls
With pointer optimization enabled, store the value of the class pointer in 33bits. In initIsa() there is newisa.shiftcls = (uintptr_t) CLS >> 3; This code stores the class pointer in isa.
magic
Used by the debugger to determine whether the current object is a real object or has no space to initialize
weakly_referenced
Weak variables that indicate whether an object is or has been referred to an ARC. Objects without weak references can be released faster.
deallocating
Flags whether the object is freeing memory.
extra_rc
extra_rc
It takes up 19 bits. The maximum reference count that can be stored should be(Why do I write this becauseextra_rc
The value -1 is saved and +1 is used to get the reference countSideTables
.SideTables
Contains aRefcountMap
Retrieves the reference count based on the address of the objectsize_t
.
Here’s the question: why use both extra_RC and SideTables?
Maybe because of history, the CPU used to be32
A,isa
There are only reference counts that can be stored. So inarm64
Under, reference counts are usually stored inisa
In the.
More details will be covered during the retain operation.
has_sidetable_rc
When the reference counter is too large, the reference count is stored in a property of a class called SideTable.
ISA_MAGIC_MASK
Obtain the magic value through the mask.
ISA_MASK
Obtain the class pointer value of ISA through the mask.
RC_ONE and RC_HALF
Correlation calculation for reference counting.
The macro in the isa_t union
SUPPORT_PACKED_ISA
Indicates whether the platform supports inserting information other than Class into isa Pointers.
- If it does, it will
Class
Information intoisa_t
Struct, and attach some other information, such as the one abovenonpointer
And so on; - If not, it will not be used
isa_t
Defined withinstruct
, at this momentisa_t
Use onlycls
(Class pointer).
On iOS and MacOSX devices, SUPPORT_PACKED_ISA is defined as 1.
SUPPORT_INDEXED_ISA
SUPPORT_INDEXED_ISA Indicates that the Class information stored in ISA_T is the address of the Class. In initIsa() there are:
#if SUPPORT_INDEXED_ISA
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
Copy the code
On iOS devices, SUPPRT_INDEXED_ISA is 0.
Isa type related macros
SUPPORT_NONPOINTER_ISA
The isa pointer used to mark whether optimization is supported or not is defined as:
#if !SUPPORT_INDEXED_ISA && !SUPPORT_PACKED_ISA
# define SUPPORT_NONPOINTER_ISA 0
#else
# define SUPPORT_NONPOINTER_ISA 1
#endif
Copy the code
How to determine whether to support optimized ISA Pointers?
- Known to iOS
SUPPORT_PACKED_ISA
To 1,SUPPORT_INDEXED_ISA
Is 0, as you can see from the above definition, for iOSSUPPORT_NONPOINTER_ISA
1; - Set in environment variables
OBJC_DISABLE_NONPOINTER_ISA
.
It is important to note that the optimized ISA pointer does not necessarily store reference counts, even in 64-bit environments. After all, it may not be enough to store reference counts in the 19-bit iOS system. The other 19 bits hold the reference count minus one.
SideTable
The SideTable structure is often seen in the source code. Its definition:
struct SideTable { spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; // omit other code};Copy the code
SideTable has three member variables:
slock
Spin-locks to ensure atomic operation;refcnts
For reference countinghash
Table;weak_table
Used for weak referenceshash
Table.
Here we will focus on the reference count hash table. Typedefs objc::DenseMap
,size_t,true> RefcountMap;
DenseMap (llvm-densemap.h) is the DenseMap class in which the SideTable is used to hold reference counts. DenseMap takes DisguisedPtr< OBJC_object > as key and size_t as value. The DisguisedPtr class is the encapsulation of the OBJC_Object * pointer and some of its operations. Its content can be understood as the object’s memory address. The value is of type __darwin_size_t, which is generally equivalent to an unsigned long in the Darwin kernel. In fact, the value saved here is equal to the reference count minus 1.
Get the reference count
RetainCount provides a reference counter, which is defined as:
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
inline uintptr_t
objc_object::rootRetainCount() {
if (isTaggedPointer()) return(uintptr_t)this; sidetable_lock(); Isa.bits = LoadExclusive(&isa.bits); Clrex ClearExclusive(& ISa.bits);if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
returnsidetable_retainCount(); Uintptr_t objc_object:: uintptr_tsidetable_retainCount() {
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if(it ! = table.refcnts.end()) { // this is validfor SIDE_TABLE_RC_PINNED too
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}
Copy the code
As you can see from the above code, there are three cases of getting a reference count:
Tagged Pointer
If so, return to ISA itself;- non
Tagged Pointer
And pointer optimization is enabled. In this case, the reference count is changed fromextra_rc
(+1 is used here, so -1 is needed when saving), and then check if there is anySideTable
If there is plus there isSideTable
Count in; - non
Tagged Pointer
, pointer optimization is not turned on, usesidetable_retainCount()
Function returns.
Impact of manual operation on reference counting
objc_retain()
#if __OBJC2__
__attribute__((aligned(16)))
id
objc_retain(id obj) {
if(! obj)return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
#else
id objc_retain(id obj) { return [obj retain]; }
Copy the code
If it is Tagged Pointer, return the object itself; otherwise, return it via retain() of the object.
inline id
objc_object::retain() { assert(! isTaggedPointer()); The hasCustomRR method checks whether a class (including its parent) contains a default methodif(fastpath(! ISA()->hasCustomRR())) {return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_retain);
}
Copy the code
This function does not want to deal with Tagged Pointer. The hasCustomRR function is then used to check whether the class (including its parent) contains a default method, and if so, to call a custom method. If not, call rootRetain().
ALWAYS_INLINE id
objc_object::rootRetain() {
return rootRetain(false.false); } // ALWAYS_INLINE ID objc_object::rootRetain(bool tryRetain, bool handleOverflow) {if (isTaggedPointer()) return(id)this; isa_t oldisa; isa_t newisa; Oldisa = LoadExclusive(&isa.bits); newisa = oldisa;if(newisa.nonpointer = 0) {// newisa.nonpointer = 0 indicates that all digits are address values // Release the lock, using the assembly instruction clrex ClearExclusive(& ISa.bits); // Since all bits are address values, use SideTable directly to store reference countsreturnsidetable_retain(); } // Store the result of extra_rc++ uintptr_t carry; // extra_rc++ newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);if(carry == 0) {// extra_rc++ overflow, carry to side table newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc =true; sidetable_addExtraRC_nolock(RC_HALF); } // write newisa to isa StoreExclusive(& ISa.bits, oldisa.bits, newISa.bits)return (id)this;
}
Copy the code
You can see from the above:
Tagged Pointer
Returns the object itself directly;newisa.nonpointer == 0
Pointer optimization is not enabledSideTable
To store reference counts;- Turn on pointer optimization using ISA
extra_rc
Save the reference count and use it when it is exceededSideTable
To store additional reference counts.
objc_release()
#if __OBJC2__
__attribute__((aligned(16)))
void
objc_release(id obj) {
if(! obj)return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
#else
void objc_release(id obj) { [obj release]; }
#endif//release() source code inline void objc_object::release() { assert(! isTaggedPointer());if(fastpath(! ISA()->hasCustomRR())) { rootRelease();return;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_release);
}
Copy the code
The logic here is the same as that of objc_retain(), so look directly at the rootRelease() function and, as above, the code below is simplified.
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow) {
if (isTaggedPointer()) return false;
isa_t oldisa;
isa_t newisa;
retry:
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (newisa.nonpointer == 0) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
uintptr_t carry;
// extra_rc--
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
if(carry == 0) {// Need to borrow from SideTable, or reference count 0 goto underflow; } // Store reference count to ISA StoreReleaseExclusive(& ISa.bits, oldisa.bits, newISa.bits)return false; Underflow: // borrow from SideTable // or call delloc with reference count 0 // omit N more code here // To sum up: change SideTable with extra_rc, // call dealloc when reference count is reduced to 0if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
}
Copy the code
You can see it from above:
- Determine whether or not
Tagged Pointer
Is returned directly; - Pointer optimization is not enabled
SideTable
Stored reference count -1; - Turn on pointer optimization using ISA
extra_rc
Save reference count -1 whencarry==0
Indicates the need toSideTable
The saved reference count is also out or the reference count is zero, so perform the last step; - The last call
dealloc
So this also answers the previous OC memory management — Object generation and destructiondealloc
When is the question called, inrootRelease(bool performDealloc, bool handleUnderflow)
The function is called if it determines that the reference count is zerodealloc
The function.
conclusion
-
Where does reference counting exist?
Tagged Pointer
Instead of reference counting, Apple simply returns the object’s pointer value as a reference count;- Pointer optimization (
nonpointer == 1
Object whose reference count exists firstisa
theextra_rc
In the more than524288
Exist.SideTable
theRefcountMap
Or rather,DenseMap
; - An object without pointer optimization enabled exists directly
SideTable
theRefcountMap
Or rather,DenseMap
In the.
-
The essence of retain/release
Tagged Pointer
Don’t participate inretain
/release
;- Find the reference count storage area, then +1/-1 and handle the carry/borrow case depending on whether pointer optimization is turned on or not;
- Called when the reference count is reduced to 0
dealloc
Function.
-
What is the isa
// ISA() assumes this is NOT a tagged pointer object Class ISA(); // getIsa() allows this to be a tagged pointer object Class getIsa(); Copy the code
- The first thing to know is that the ISA pointer is not necessarily a class pointer anymore, so you need to use it
ISA()
Get a class pointer; Tagged Pointer
Object does not haveisa
Pointers, some of themisa_t
Structure of;- Isa Pointers to other objects are still class Pointers.
- The first thing to know is that the ISA pointer is not necessarily a class pointer anymore, so you need to use it
-
What is the value of the object
- If it is
Tagged Pointer
, the value of an object is a pointer; - If the
Tagged Pointer
The value of the object is the value of the memory region to which the pointer points.
- If it is
Addendum: a multi-threaded security topic
The following code results
@property (nonatomic, strong) NSString *target;
//....
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
dispatch_async(queue, ^{
self.target = [NSString stringWithFormat:@"ksddkjalkjd%d",i];
});
}
Copy the code
C. Crash D. Crash
Cause of Crash: Excessive release.
Key points:
- In the asynchronous execution of global queue and custom parallel queue, the number of threads will be determined according to the task system.
target
usestrong
The Block intercepts the modifier of the object;- Even using
_target
The effect is the same because it is used by defaultstrong
An implicit modification; strong
The source code is as follows:
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
Copy the code
Suppose that this concurrent queue creates two threads, A and B, which, since they are asynchronous, can execute simultaneously. So there is A scenario where in thread A the code executes to objc_retain(obj), but in thread B it may execute to objc_release(prev), at which point the prev has been released. So when A executes to objc_release(prev), it will overrelease, causing the program to crash.
Solutions:
- Add a mutex
- If you use a serial queue, if you use a serial queue, it’s actually internally dependent
DISPATCH_OBJ_BARRIER_BIT
Sets the blocking flag bit - The use of weak
- Using Tagged Pointer