Android源码中,C++层面中的强指针和弱指针实现原理。
说明:该部分代码已经移植到Linux系统上,具体代码以下面的示例代码均可以在我的
github repo中下载到。
为何要用强、弱指针
C/C++中内存管理一直是让程序员头疼的地方,申请一段内存要记得释放,否则就会内存泄漏,
它不像Java能够自动回收。Android框架部分大量使用C++实现,内存管理尤为重要,而最简单
的自动内存管理方法就是计数
,每个对象都有一个计数器,当增加引用时计数器增加,当
减少引用时计数器减小,当计数器减为0时,对象自动释放。强、弱指针的根本原理也是这样的。
但是简单的计数方法有个缺陷,那就是循环引用
,当两个对象循环引用时,谁都无法释放,
从而导致内存泄漏,Android引入弱指针的概念,从而解决了循环引用的问题,而且弱引用在
实现对象缓存
方面也是非常方便的,这两个场景是弱引用的主要服务对象。弱引用的存在主要
解决了这样的问题:即使存在多个弱引用,但是对象仍然可以正常释放。
强、弱指针的实现原理
具体如何实现呢,一般的实现方法是给每个对象添加一个计数器,其实就是一个int
字段,
但是考虑到弱指针需要弱引用计数器,Android并不是简单的添加一个计数器,而是为每个对象
创建一个额外的弱引用对象,叫做weakref_type,它里面包括强引用计数器、弱引用计数器、
base对象指针以及对象生命周期标志位,base对象和其弱引用对象在内存中的示意图如下,
1 | /* |
两个对象总是成对儿存在的,它们总是共进退。
RefBase对象只有一个字段: mRefs
,它是一个指向计数对象(weakref_type)对象的指针。
RefBase类中定义了强引用相关的操作,比如incStrong()
、decStrong()
等,而weakref_type
类中定义了弱引用相关的操作,比如incWeak()
、decWeak()
、promote()
等操作,如图示,
对象的布局定义好了,我们还需要操作对象的手段,以达到**自动**
释放对象的目的,为此
android创建了template <typename T> class sp
和template <typename T> class sp
两个类,sp
对象只有一个字段,那就是RefBase
对象的指针,wp
对象中有两个字段,分别是指向RefBase
和weakref_type
对象的指针,
然后这两个类中重载了一堆操作符,如赋值操作,解引用操作,比较运算符等等,以至于我们
可以像使用base object一样使用sp
和wp
对象。
然后就是关键的构造函数和析构函数,所谓的自动操作均在sp和wp的构造函数中实现,
- sp在构造函数中增加强指针计数,在析构函数中减小强指针计数,
- wp在构造函数中增加弱指针计数,在析构函数中减小弱指针计数。
而sp, wp对象是在栈(stack)上创建的(是的,这是使用强弱指针的关键),所以sp, wp对象在
跳出作用域之后会自动析构,这是整个系统实现的关键所在。
源码分析
代码主要分为三部分:
- 强指针的相关操作,在RefBase类中
- 弱指针的相关操作,在RefBase::weakref_type类中
- sp, wp实现自动化操作
原则上来讲,强指针只修改强引用计数,弱指针只修改弱引用计数,它们只关注自己
的计数器而不能染指其他计数器,但是强弱指针之间又有关联,那就是“强引用的增减
一定会导致弱引用的增减”。
强指针的相关操作
强指针的操作主要是:incStrong()
和decStrong()
,它们实现强引用计数的增减。extendObjectLifetime()
是protected的,它可以修改mFlag
标志位,该标志位用来
控制对象的生命周期,当前有两种生命周期:
- OBJECT_LIFETIME_STRONG: 对象生命周期由强指针控制,即强引用为0时释放对象内存
- OBJECT_LIFETIME_WEAK: 对象生命周期由弱指针控制,即弱引用为0时释放对象内存
另外,onFirstRef()
,onLastStrongRef()
,顾名思义,在第一次和最后一次引用时
调用,子类可以覆盖这些方法,有点儿“构造”、“析构”的意思,onIncStrongAttempted()
函数是弱引用promote为强引用时用到的,子类可以覆盖该方法,用于表明自己是否愿意
由弱转强,即给智能指针系统一个提示从而表明自己的态度,onXXX()这几个函数可以
理解为智能指针系统留给之类的接口,从而让子类一定程度上参与到指针的管理中来。
下面单独分析incStrong()
,decStrong()
和~RefBase()
,这几个函数是强指针实现
的关键。
1 | void RefBase::incStrong(const void* id) const |
1 | void RefBase::decStrong(const void* id) const |
1 | RefBase::RefBase() |
总结:
incStrong()
: inc weak counter => inc strong counter, 如果是第一个强指针,调用子类接口进行初始化工作decStrong()
: dec strong counter => dec weak counter, 如果是最后一个强指针,调用子类接口进行清理工作~RefBase()
: 释放计数器对象,计数器对象是RefBase对象的一部分,所以在析构时要记得释放资源,这要跟decWeak()
配合进行,否则多次delete是会出问题的。
弱指针的相关操作
弱引用的操作集中在:incWeak()
, decWeak()
, attemptIncStrong()
三个操作上。
弱引用存在的目的是:即使存在多个弱引用,对象仍然可以正常释放。弱引用对象(wp)不能直接操作对象,因为它没有
覆盖解引用操作符和指针操作符(->),要想操作对象,只能先将弱引用对象(wp)提升为强引用对象(sp),然后才能进行
操作,而提升要通过attemptIncStrong()
才能进行。
1 | void RefBase::weakref_type::incWeak(const void* id) |
1 | void RefBase::weakref_type::decWeak(const void* id) |
流程图如下,
1 | bool RefBase::weakref_type::attemptIncStrong(const void* id) { |
流程图如下,注意这个流程图不是完全按照代码顺序来的,但是逻辑上是等价的,
总结:
弱指针的相关操作是比较复杂的,因为他要考虑强指针的一些情况,还要进行promote由弱转强,
这几个函数的逻辑是比较复杂的。promote时一个先决条件就是:RefBase对象是存在的,如果它
不存在,那么wp的promote压根儿不会进行。
sp, wp的相关操作
- sp只会用到两个函数:
incStrong()
,decStrong()
,分别在sp的构造(或赋值)、析构时用到 - wp除了用到
incWeak()
,decWeak()
之外,在其promote时还会用到onIncStrongAttempted()
,
分别在wp的构造(或赋值)、析构以及promote时用到。
强、弱指针的使用方法
用法,是的,原理逻辑复杂,但是用法却很简单。
1 | class A : public RefBase { |