Android系统智能指针的设计思路(轻量级指针、强指针、弱指针)
本博客为原创,转载请注明出处,谢谢。
参考博文:Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析
C++中最容易出错的地方莫过于指针了,指针问题主要有两类,一是内存泄露,二是无效引用。new出来的对象忘记delete,造成这部分内存无法使用无法回收,引起内存泄露的问题;多个指针指向同一个对象,在一处delete之后,其他指针在不知情的情况下继续引起访问错误,甚至形成一个引发恶意攻击的漏洞。
Android底层是由C++实现的,在指针和对象的管理上也下了不少的功夫,实现的智能指针也不仅仅是多一个引用则引用数加一,移除一个引用引用数减一,为0后删除那么简单。可以说Android的智能指针是一套体系,实现的相当精妙。
不想看代码的解析的可以直接拉到最后看结论。
代码选择的4.0.1版本。
基本智能指针的实现:
智能指针是通过引用技术实现指针指向的对象的共享,对象的创建和删除(主要是删除)交给智能指针处理,而不用用户过分关心。
实现智能指针需要两步:一是为指向的对象关联引用计数,二是构造智能指针对象。
引用计数是目标对象的属性,实现方法是:编写基类,实现引用计数的维护,然后让指向的类继承该基类,获得引用技术的属性。该基类在Android系统中为LightRefBase和RefBase,实现在frameworks\native\include\utils\RefBase.h中。
智能指针本身是一个对象,负责维护引用计数并根据引用计数delete引用对象。
Android系统中普通智能指针--轻量级指针
轻量级指针基本智能指针的实现,通过一个计数器保持对象的引用。
template <class T> class LightRefBase { public: inline LightRefBase() : mCount(0) { } inline void incStrong(const void* id) const { android_atomic_inc(&mCount); } inline void decStrong(const void* id) const { if (android_atomic_dec(&mCount) == 1) { delete static_cast<const T*>(this); } } //! DEBUGGING ONLY: Get current strong ref count. inline int32_t getStrongCount() const { return mCount; } typedef LightRefBase<T> basetype; protected: inline ~LightRefBase() { } private: friend class ReferenceMover; inline static void moveReferences(void* d, void const* s, size_t n, const ReferenceConverterBase& caster) { } private: mutable volatile int32_t mCount; };
这个类中最重要的是 mutable volatile int32_t mCount; 这个类的任务就是维护mCount这个引用计数器, mutable实现互斥访问,volatile保证值的最新,类提供增加和减少mCount的方法,当引用为1然后再减少时则删除对象。
继承于该类的对象的智能指针在构建新对象,执行拷贝构造函数以及析构函数时会调用其中的方法
sp<T>::sp(T* other) : m_ptr(other) { if (other) other->incStrong(this); } sp<T>::sp(const sp<T>& other) : m_ptr(other.m_ptr) { if (m_ptr) m_ptr->incStrong(this); } sp<T>::~sp() { if (m_ptr) m_ptr->decStrong(this); }
Android系统轻量级指针就是实现了基本的智能指针,比较容易理解。这种形式的智能指针很大程度上解决了C++指针问题,但如下场景还会力不从心。
系统中有两个对象A和B,在对象A的内部引用了对象B,而在对象B的内部也引用了对象A。当两个对象A和B都不再使用时,系统会发现无法回收这两个对象的所占据的内存的,因为系统一次只能回收一个对象,而无论系统决定要收回对象A还是要收回对象B时,都会发现这个对象被其它的对象所引用,因而就都回收不了,类似于死锁现象,这样就造成了内存泄漏。
针对这个问题,可以采用对象的引用计数同时存在强引用和弱引用两种计数。例如A引用B则B的强引用计数和弱引用计数+1,而B引用A则A仅仅弱引用数+1,在回收时只要对象的强引用计数为0,则不管弱引用数是否为0都进行回收,类似于死锁解决中的强制释放资源,这样问题得到解决。
Android中的强指针和弱指针
以下有几个概念容易混淆,先提出来辨析
强引用计数和弱引用计数:目标对象关联的两个计数属性,这两个计数属性同时存在,值由指针控制,不一定相等。
强指针和弱指针:这是两个不同的智能指针对象,在创建对象、拷贝构造以及析构的时候改变引用对象的强引用计数和弱引用计数,两种指针的改变规则不同。
对象采用的引用计数方法:用flag表示目标对象在回收时受哪种强引用计数还是弱引用计数影响,4.0版本没有了forever模式。
引用计数类代码如下:
class RefBase { public: void incStrong(const void* id) const; void decStrong(const void* id) const; void forceIncStrong(const void* id) const; //! DEBUGGING ONLY: Get current strong ref count. int32_t getStrongCount() const; class weakref_type { public: RefBase* refBase() const; void incWeak(const void* id); void decWeak(const void* id); // acquires a strong reference if there is already one. bool attemptIncStrong(const void* id); // acquires a weak reference if there is already one. // This is not always safe. see ProcessState.cpp and BpBinder.cpp // for proper use. bool attemptIncWeak(const void* id); //! DEBUGGING ONLY: Get current weak ref count. int32_t getWeakCount() const; //! DEBUGGING ONLY: Print references held on object. void printRefs() const; //! DEBUGGING ONLY: Enable tracking for this object. // enable -- enable/disable tracking // retain -- when tracking is enable, if true, then we save a stack trace // for each reference and dereference; when retain == false, we // match up references and dereferences and keep only the // outstanding ones. void trackMe(bool enable, bool retain); }; weakref_type* createWeak(const void* id) const; weakref_type* getWeakRefs() const; //! DEBUGGING ONLY: Print references held on object. inline void printRefs() const { getWeakRefs()->printRefs(); } //! DEBUGGING ONLY: Enable tracking of object. inline void trackMe(bool enable, bool retain) { getWeakRefs()->trackMe(enable, retain); } typedef RefBase basetype; protected: RefBase(); virtual ~RefBase(); //! Flags for extendObjectLifetime() enum { OBJECT_LIFETIME_STRONG = 0x0000, OBJECT_LIFETIME_WEAK = 0x0001, OBJECT_LIFETIME_MASK = 0x0001 }; void extendObjectLifetime(int32_t mode); //! Flags for onIncStrongAttempted() enum { FIRST_INC_STRONG = 0x0001 }; virtual void onFirstRef(); virtual void onLastStrongRef(const void* id); virtual bool onIncStrongAttempted(uint32_t flags, const void* id); virtual void onLastWeakRef(const void* id); private: friend class ReferenceMover; static void moveReferences(void* d, void const* s, size_t n, const ReferenceConverterBase& caster); private: friend class weakref_type; class weakref_impl; RefBase(const RefBase& o); RefBase& operator=(const RefBase& o); weakref_impl* const mRefs; };
和轻量级指针LightRefBase相比,RefBase最重要的区别是引用计数变成了weakref_impl* const mRefs
class RefBase::weakref_impl : public RefBase::weakref_type { public: volatile int32_t mStrong; volatile int32_t mWeak; RefBase* const mBase; volatile int32_t mFlags; weakref_impl(RefBase* base) : mStrong(INITIAL_STRONG_VALUE) , mWeak(0) , mBase(base) , mFlags(0) { } }
可以看到weakref_impl包含了四个属性字段,而LightRefBase中只有mCount,其中mStrong是强引用计数, mWeak是弱引用计数, mBase指向宿主类, mFlags标识对象受哪种计数方式影响。
其中mFlags可选的值是 OBJECT_LIFETIME_WEAK = 0x0000 :表示受强引用计数影响; OBJECT_LIFETIME_WEAK = 0x0001:受弱引用计数影响; OBJECT_LIFETIME_MASK = 0x0001:这个还没太懂
mFlags默认值为0,通过 extendObjectLifetime(int32_t mode) 函数改变其值。
这个类中其他重要的几个函数就是 incStrong, decStrong, incWeak 和 decWeak了,其中incStrong并比是简单的把mStrong的值加1,这几个计数的增加和减少都有一系列的规则。
incStrong函数:
void RefBase::incStrong(const void* id) const { weakref_impl* const refs = mRefs; refs->incWeak(id); refs->addStrongRef(id); const int32_t c = android_atomic_inc(&refs->mStrong); ALOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs); #if PRINT_REFS ALOGD("incStrong of %p from %p: cnt=%d\n", this, id, c); #endif if (c != INITIAL_STRONG_VALUE) { return; } android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong); refs->mBase->onFirstRef(); }
可以看到incStrong函数中做了三件事,1:增加弱引用计数;2:增加强引用计数;3:如果是第一次调用,则调用onFirstRef()
强弱智能指针最重要的是在减少引用计数时的操作。
decStrong:
void RefBase::decStrong(const void* id) const { weakref_impl* const refs = mRefs; refs->removeStrongRef(id); const int32_t c = android_atomic_dec(&refs->mStrong); #if PRINT_REFS ALOGD("decStrong of %p from %p: cnt=%d\n", this, id, c); #endif ALOG_ASSERT(c >= 1, "decStrong() called on %p too many times", refs); if (c == 1) { refs->mBase->onLastStrongRef(id); if ((refs->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) { delete this; } } refs->decWeak(id); }
decWeak:
void RefBase::weakref_type::decWeak(const void* id) { weakref_impl* const impl = static_cast<weakref_impl*>(this); impl->removeWeakRef(id); const int32_t c = android_atomic_dec(&impl->mWeak); ALOG_ASSERT(c >= 1, "decWeak called on %p too many times", this); if (c != 1) return; if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) { // This is the regular lifetime case. The object is destroyed // when the last strong reference goes away. Since weakref_impl // outlive the object, it is not destroyed in the dtor, and // we'll have to do it here. if (impl->mStrong == INITIAL_STRONG_VALUE) { // Special case: we never had a strong reference, so we need to // destroy the object now. delete impl->mBase; } else { // ALOGV("Freeing refs %p of old RefBase %p\n", this, impl->mBase); delete impl; } } else { // less common case: lifetime is OBJECT_LIFETIME_{WEAK|FOREVER} impl->mBase->onLastWeakRef(id); if ((impl->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK) { // this is the OBJECT_LIFETIME_WEAK case. The last weak-reference // is gone, we can destroy the object. delete impl->mBase; } } }
将强引用计数减1,onLastStrongRef函数为空,不用考虑。
当减少强引用计数前为1,即现在强引用计数为0,则执行if ((refs->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG),其中OBJECT_LIFETIME_MASK值为0x0001,和WEAK的相同。条件在只有在mFlags为0时成立。这时候说明对象受强引用计数影响,所有删除对象。
refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK。类似条件式子的理解。此式子在mFlags为0的时候为真,mFlags为OBJECT_LIFETIME_WEAK或者OBJECT_LIFETIME_FOREVER是为假,代码中有不少类似式子,为什么没有直接判断而是用这样的复杂式子我理解是因为表达的一致性和代码的可读性。
在decWeak函数中,如果调用到这里,那么就说明前面一定有增加过此对象的弱引用计数,而增加对象的弱引用计数有两种场景的,一种场景是增加对象的强引用计数的时候,会同时增加对象的弱引用计数,另一种场景是当我们使用一个弱指针来指向对象时,在弱指针对象的构造函数里面,也会增加对象的弱引用计数,不过这时候,就只是增加对象的弱引用计数了,并没有同时增加对象的强引用计数。因此,这里在减少对象的弱引用计数时,就要分两种情况来考虑。
如果是前一种场景,这里的impl->mStrong就必然等于0,而不会等于INITIAL_STRONG_VALUE值,因此,这里就不需要delete目标对象了(impl->mBase),因为前面的RefBase::decStrong函数会负责delete这个对象。这里唯一需要做的就是把weakref_impl对象delete掉。
如果是后一种情景,这里的impl->mStrong值就等于INITIAL_STRONG_VALUE了,这时候由于没有地方会负责delete目标对象,因此,就需要把目标对象(imp->mBase)delete掉了,否则就会造成内存泄漏。在delete这个目标对象的时候,就会执行RefBase类的析构函数,这时候目标对象的弱引用计数等于0,于是,就会把weakref_impl对象也一起delete掉了。
到目前为止,讨论的是强引用计数和弱引用计数在增加和减少时的情况,规则见最后的结论。
强指针sp和弱指针wp
强指针sp定义在frameworks\native\include\utils\StrongPointer.h
弱指针定期在frameworks\native\include\utils\RefBase.h
与强指针类相比,它们都有一个成员变量m_ptr指向目标对象,但是弱指针还有一个额外的成员变量m_refs,它的类型是weakref_type指针。
强指针直接调用incStrong和decStrong,会同时增加或减少强引用计数和弱引用计数。
弱指针调用createWeak进而调用incWeak,或者调用decWeak,只影响弱引用计数,不影响强引用计数。
弱指针的最大特点是它不能直接操作目标对象,这是怎么样做到的呢?秘密就在于弱指针类没有重载*和->操作符号,而强指针重载了这两个操作符号。但是,如果我们要操作目标对象,应该怎么办呢,这就要把弱指针升级为强指针了。
弱指针升级为强指针通过promote函数实现
sp<T> wp<T>::promote() const { sp<T> result; if (m_ptr && m_refs->attemptIncStrong(&result)) { result.set_pointer(m_ptr); } return result; }
如果m_ptr不为空并且attemptIncStrong成功,则通过弱指针的m_ptr构建强指针。
bool RefBase::weakref_type::attemptIncStrong(const void* id) { incWeak(id); weakref_impl* const impl = static_cast<weakref_impl*>(this); int32_t curCount = impl->mStrong; ALOG_ASSERT(curCount >= 0, "attemptIncStrong called on %p after underflow", this); while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) { if (android_atomic_cmpxchg(curCount, curCount+1, &impl->mStrong) == 0) { break; } curCount = impl->mStrong; } if (curCount <= 0 || curCount == INITIAL_STRONG_VALUE) { bool allow; if (curCount == INITIAL_STRONG_VALUE) { // Attempting to acquire first strong reference... this is allowed // if the object does NOT have a longer lifetime (meaning the // implementation doesn't need to see this), or if the implementation // allows it to happen. allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK || impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id); } else { // Attempting to revive the object... this is allowed // if the object DOES have a longer lifetime (so we can safely // call the object with only a weak ref) and the implementation // allows it to happen. allow = (impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_WEAK && impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id); } if (!allow) { decWeak(id); return false; } curCount = android_atomic_inc(&impl->mStrong); // If the strong reference count has already been incremented by // someone else, the implementor of onIncStrongAttempted() is holding // an unneeded reference. So call onLastStrongRef() here to remove it. // (No, this is not pretty.) Note that we MUST NOT do this if we // are in fact acquiring the first reference. if (curCount > 0 && curCount < INITIAL_STRONG_VALUE) { impl->mBase->onLastStrongRef(id); } } impl->addStrongRef(id); #if PRINT_REFS ALOGD("attemptIncStrong of %p from %p: cnt=%d\n", this, id, curCount); #endif if (curCount == INITIAL_STRONG_VALUE) { android_atomic_add(-INITIAL_STRONG_VALUE, &impl->mStrong); impl->mBase->onFirstRef(); } return true; }
总结:
1. Android中LightRefBase实现了基本的智能指针功能。
2. 强指针和弱指针是为了解决循环引用中类死锁问题引起的内存泄露。
3. 引用对象继承RefBase,则拥有了强引用计数和弱引用计数的属性。
4. 通过extendObjectLifetime函数改变mFlags的值,确定对象受哪种引用计数影响。
5. 调用incStrong增加强引用计数时,会同时增加强引用计数和弱引用计数,第一次增加强引用计数时,强引用计数从INITIAL_STRONG_VALUE改变为1。
6. 调用incWeak增加弱引用计数时,只有弱引用计数增加,强引用计数不变。
7. 弱引用计数≥强引用计数,强增弱必增,弱增强不一定增。
8. 调用decStrong减少强引用指针时:
如果对象受强引用计数影响,强引用计数减1,如果此时强引用计数为0,不管弱引用计数是否为0,删除引用对象(此时由于弱引用计数还没减少,所以析构函数不会删除指针对象)。然后弱引用计数减1,如果此时弱引用计数也为0,则删除指针对象。
如果对象受弱引用计数影响,强引用计数减1,弱引用计数减1,如果此时弱引用计数为0,则删除引用对象,在引用对象的析构函数中会删除指针对象。
9. 调用decWeak减少弱引用指针时:
如果对象受强引用计数影响,弱引用计数减1,如果此时弱引用计数为0,看看强引用计数,如果为INITIAL_STRONG_VALUE则说明没有强指针指向该对象,此时直接删除引用对象,指针对象也会随之删除。如果强引用计数不为INITIAL_STRONG_VALUE,则一定为0,因为强引用计数小于等于弱引用计数,此时只需要删除指针对象,因为强引用计数为0并且对象受强引用计数影响,所以对象已经被删除。
如果对象受弱引用计数影响,弱引用计数减1,如果此时弱引用计数为0,则删除引用对象,指针对象也随之删除。
10. 强指针和弱指针是两个不同的类,引用对象时会以不同的方式改变强引用计数和弱引用计数的值。
11. 强指针直接调用incStrong和decStrong,会同时增加或减少强引用计数和弱引用计数。弱指针调用createWeak进而调用incWeak,或者调用decWeak,只影响弱引用计数,不影响强引用计数。
12. 弱指针不能直接操作目标对象,因为弱指针类没有重载*和->操作符号,而强指针重载了这两个操作符号。如果我们要操作目标对象,就要使用promote函数把弱指针升级为强指针。
13. 弱指针转化为强指针不一定成功,如果可以转化则使用弱指针的m_ptr构造强指针。
14. 智能指针方案实现的很精妙,建议读源码去体会,我参考的4.0的源码注释也比较清晰,能帮助理解。