ZT Android的引用计数(强弱指针)技术及一些问题
Android的引用计数(强弱指针)技术及一些问题
Android C++框架层的引用计数技术
C++ 中对指针的使用时很头疼的事情,一个是经常会忘记free 指针,造成内存泄露,另外一个就是野指针问题:访问已经free掉的指针。程序debug工作的相当大部分,都是花费在这。Android中通过引用计数 来自动管理指针的生命周期,动态申请的内存将会在不再需要时被自动释放(有点类似Java的垃圾回收),不用程序员明确使用delete来释放对象,也不 需要考虑一个对象是否已经在其它地方被释放了,从而使程序编写工作减轻不少,而程序的稳定性也大大提高。
Android提供的引用计数技术,主要是通过RefBase类及其子类sp (strong pointer)和wp(weak pointer)实现的,具体的原理和细节就不说了,可以参看《深入理解Android:卷1》,说的还是比较清楚。
引用计数的问题
任何东西都不会是万能的,Android C++中的引用计数问题,和Java一样,并不能完全避免内存泄露,另外还有一个问题就是性能(overhead)问题也很突出,本文就不说了。
使用了android的sp指针,也不能说就不需要程序员去关心指针的细节了。通常由于设计或使用的不良,更有可能导致内存无法回收,也就是内存泄 露问题,甚至于还可能导致不明就里的野指针问题。而此时导致的内存使用问题,由于其具有更多的欺骗性和隐蔽性,往往更难发觉和调试。
循环引用及其解法
使 用引用计数的智能指针管理方法中,常见的java内存泄露问题在C++中一样存在。在Android的强指针引用中,一个最常见的就是强指针的循环引用问 题。而这又是程序员比较容易犯的问题:在程序员对强弱指针的理解不是很深入的情况下,想当然的认为使用了强指针,系统会根据引用计数自动收回。
循 环引用,就是对象A有个强指针,引用对象B;对象B中,也有个强指针,引用对象A;这样A和B就互锁。A对象释放B对象的引用是在本身被析构回收时,而析 构回收的前提是A对象没有被引用,则需要B对象先释放,B对象释放的前提是A对象释放...如此则A和B都无法释放,这样即产生了内存泄露。
我们可以写个程序看一下这种泄露情况。
先定义一个类,这里假定叫Bigclass:
- namespace android{
- class Bigclass : public RefBase
- {
- public:
- Bigclass(char *name){
- strcpy(mName, name);
- ALOGD("Construct: %s", mName);
- }
- ~Bigclass(){
- ALOGD("destruct: %s", mName);
- }
- void setStrongRefs(sp<Bigclass> b){
- spB = b;
- }
- private:
- sp<Bigclass> spB;
- char mName[64];
- };
- }
该类非常简单,只有一个sp指针和一个name成员。循环引用示例:
- void testStrongCrossRef(){
- sp<Bigclass> A = new Bigclass("testStrongClassA");
- sp<Bigclass> B = new Bigclass("testStrongClassB");
- A->setStrongRefs(B);
- B->setStrongRefs(A);
- }
- int main(){
- ALOGD("Start testStrongClasses..");
- testStrongCrossRef();
- ALOGD("testStrongClasses Should be destructed!!");
- return 0;
- }
输出的结果,如预期,对象没有被释放,泄露了:
- D/TEST ( 1552): Start testStrongClasses..
- D/TEST ( 1552): Construct: testStrongClassA
- D/TEST ( 1552): Construct: testStrongClassB
- D/TEST ( 1552): testStrongClasses Should be destructed!!
为了解决这一问题,Android在又引入了弱指针,弱指针并不能通过引用计数来控制所引用对象的生命周期,这样就可以消除上例中的引用环路问题,使得问题解决。我们将上述的类稍作修改,增加了弱引用的接口:
- namespace android{
- class Bigclass : public RefBase
- {
- public:
- Bigclass(char *name){
- strcpy(mName, name);
- ALOGD("Construct: %s", mName);
- }
- ~Bigclass(){
- ALOGD("destruct: %s", mName);
- }
- void setStrongRefs(sp<Bigclass> b){
- spB = b;
- }
- void setWeakRefs(sp<Bigclass> b){
- wpB = b;
- }
- private
- sp<Bigclass> spB;
- wp<Bigclass> wpB;
- char mName[64];
- };
- }
先来测试一下,将上例中的强指针换成弱指针,会是什么情况:
- void testWeakCrossRef(){
- sp<Bigclass> A = new Bigclass("testWeakClassA");
- sp<Bigclass> B = new Bigclass("testWeakClassB");
- A->setWeakRefs(B);
- B->setWeakRefs(A);
- }
输出结果:
- D/TEST ( 2889): Start testWeakClass ..
- D/TEST ( 2889): Construct: testWeakClassA
- D/TEST ( 2889): Construct: testWeakClassB
- D/TEST ( 2889): destruct: testWeakClassB
- D/TEST ( 2889): destruct: testWeakClassA
- D/TEST ( 2889): testWeakClass Should be destructed!!
在出了testWeakClassA和testWeakClassB在对象A和B出了作用域后,没有强引用了,两个对象都释放了,这个符合预期。
这里testWeakClassA和testWeakClassB之间的引用关系,全部都是弱引用,因此二者间的生命周期互不相干,这里二者用
sp<Bigclass>对象A和B与创建一般的栈对象 Bigclass A, Bigclass B 的生命周期一样。
Android中,最常用的肯定不是上面两种:
- 强强引用——互不相让,互相绑死,这是绝对禁止的。
- 弱弱引用——互不相干,各管生死。这个对于想要使用引用计数自动管理对象生命周期来说,没什么用处。
最常用的一般是强弱引用关系。强弱引用需要是有从属关系的,具体那个类是用sp引用,哪个是用wp引用,要看设计的逻辑了。
测试示例:
- void testCrossRef(){
- sp<Bigclass> A = new Bigclass("testNormalClassA");
- sp<Bigclass> B = new Bigclass("testNormalClassB");
- A->setStrongRefs(B);
- B->setWeakRefs(A);
- }
输出结果:
- D/TEST ( 2889): Start test Normal pointer reference ..
- D/TEST ( 2889): Construct: testNormalClassA
- D/TEST ( 2889): Construct: testNormalClassB
- D/TEST ( 2889): destruct: testNormalClassA
- D/TEST ( 2889): destruct: testNormalClassB
- D/TEST ( 2889): Test Normal pointer reference Should be destructed!!
这种情况下,消除了循环引用,没有了内存泄露问题。
和上一个弱弱引用的例子比较,这里testNormalClassB的析构在testWeakClassA之后,testWeakClassB的生命周期
是受testWeakClassA控制的,只有testWeakClassA析构,testWeakClassB才会析构。(上面的弱弱引用的测例,说明
在无干预的情况下,应该是testWeakClassB先析构的)
对于强弱指针的使用,使用弱指针是需要特别注意,弱指针指向的对象,可能已经被销毁了,使用前需要通过promote()方法探测一下,详细信息可参考《深入理解Android》
野指针问题
强弱引用的问题,相信大多数Android程序员都明白,这里主要要强调一点就是:使用的时候要小心,一不小心可能就出错,举个例子:
我们在刚才定义的BigClass中增加另外一个构造函数:
- Bigclass(char *name, char * other){
- strcpy(mName, name);
- ALOGD("Construct another: %s", mName);
- setWeakRefs(this);
- }
这个构造函数,是将本对象中wp类型成员变量的用自己构造,也就是wp指针指向自己,这是允许的。
在写个测试用例:
- void testCrash(){
- sp<Bigclass> A = new Bigclass("testCrashA", "testCrash");
- sp<Bigclass> B = new Bigclass("testCrashB");
- A->setWeakRefs(B);
- B->setWeakRefs(A);
- }
输出结果:
- D/TEST ( 3709): Construct another: testCrashA
- D/TEST ( 3709): destruct: testCrashA
- D/TEST ( 3709): Construct: testCrashB
- D/TEST ( 3709): destruct: testCrashB
好像没有什么问题,程序也没有崩溃呀?
没有崩溃,那是幸运,因为这个测试代码和上下文太简单了。 我们看下输出就知道了:testCrashB对象构造的时候, testClassA已经析构了!!!!
也就是说,A对象,在其创建后,马上就消亡了,testCrash()方法中操作的A对象所指向的,都是野指针!!!
为何会出现野指针?问题出在刚才定义的构造函数Bigclass(char *name, char * other)中。
- setWeakRefs(this);
这里的this是一个Bigclass *类型的,这样在参数压栈的时候需要构建一个临时的sp<Bigclass>强指针对象,调用完成后,该对象析构。该sp对象通过sp的sp<T*>构造函数构造的。
这里我们看一下这个临时sp对象的构造和析构过程:
构造完成后:new出来的这个Bigclass对象testCrashA,这个RefBase的子类对象,其强引用计数为1(从INITIAL_STRONG_VALUE增加到1)
析构完毕后:sp的析构会减小该Bigclass对象指针(即对象
testCrashA,也是这里的this指针指向的对象)的强引用的计数,这里是从1减小到INITIAL_STRONG_VALUE,当一个对象的引
用计数减小到INITIAL_STRONG_VALUE时,会触发该Bigclass对象的delete操作,也就析构了该this指针指向的对象了。
这里,构造函数中就删除构造的对象,比较难想象吧!有兴趣可以验证一下看看,将刚才的构造函数修改一下:
- Bigclass(char *name, char * other){
- ALOGD("start Construct another: %s,", mName);
- strcpy(mName, name);
- setWeakRefs(this);
- ALOGD("end Construct another: %s,", mName);
- }
看一下析构是否在start和end之间。跑一下,你会有意想不到的打印 :)
这里可以给一条规则:绝对不能在创建的RefBase对象还没有被一个确定的长作用域sp对象引用前,通过局部短作用域sp指针引用。
建议
没什么好建议,搞明白设计意图和工作原理对调试和写代码都非常有好处。
ZT Android的引用计数(强弱指针)技术及一些问题的更多相关文章
- COM笔记-引用计数
参考网站:https://www.cnblogs.com/fangyukuan/archive/2010/06/06/1752621.html com组件将维护一个称作是引用计数的数值.当客户从组件取 ...
- C++ 引用计数技术及智能指针的简单实现
一直以来都对智能指针一知半解,看C++Primer中也讲的不够清晰明白(大概是我功力不够吧).最近花了点时间认真看了智能指针,特地来写这篇文章. 1.智能指针是什么 简单来说,智能指针是一个类,它对普 ...
- 【转载】C++应用引用计数技术
原帖:http://www.cnblogs.com/chain2012/archive/2010/11/12/1875578.html 因为Windows的内核对象也运用了引用计数,所以稍作了解并非无 ...
- c++沉思录 学习笔记 第六章 句柄(引用计数指针雏形?)
一个简单的point坐标类 class Point {public: Point():xval(0),yval(0){} Point(int x,int y):xval(x),yval(y){} in ...
- 引用计数 vs. GC
内存管理问题 内存管理是编程过程中的一个经典问题,早期在 C 语言时代,几乎都靠 malloc/free 手动管理内存.随着各个平台的发展,到现在被广泛采用的主要有两个方法: 引用计数 (ARC,Au ...
- iOS内存管理系列之一:对象所有权与引用计数
当一个所有者(owner,其本身可以是任何一个Objective-C对象)做了以下某个动作时,它拥有对一个对象的所有权(ownership): 1. 创建一个对象.包括使用任何名称中包含“alloc” ...
- C++中的智能指针、轻量级指针、强弱指针学习笔记
一.智能指针学习总结 1.一个非const引用无法指向一个临时变量,但是const引用是可以的! 2.C++中的delete和C中的free()类似,delete NULL不会报"doubl ...
- iOS开发--引用计数与ARC
以下是关于内存管理的学习笔记:引用计数与ARC. iOS5以前自动引用计数(ARC)是在MacOS X 10.7与iOS 5中引入一项新技术,用于代替之前的手工引用计数MRC(Manual Refer ...
- Python 对象的引用计数和拷贝
Python 对象的引用计数和拷贝 Python是一种面向对象的语言,包括变量.函数.类.模块等等一切皆对象. 在python中,每个对象有以下三个属性: 1.id,每个对象都有一个唯一的身份标识自己 ...
随机推荐
- 一句话讲清URI、URL、URN
关于URI,URL ,URN URN(Uniform Resource Name):统一资源名称 URL(Uniform Resource Locator):统一资源定位符 URI(Uniform R ...
- Markdown 语法整理大集合2017
简明教程:https://ouweiya.gitbooks.io/markdown/ 1.标题 代码 注:# 后面保持空格 # h1 ## h2 ### h3 #### h4 ##### h5 ### ...
- 网页布局之Div
div(division分区) 它是一个块标签,主要用来把网页中相关的内容组织到一起 你可以把网页的头部放到一个标签中,主体部分放到另一个标签中 使用class类名描述div内容 要想区分每个div, ...
- Github - 修改语言统计
前些日子看到有人提到这个问题,于是自己也试着解决了一番,在此记录下来,希望对大家有帮助. Github中创建一个repository后会出现一个统计使用语言的颜色条. 就是下面这个东西: 似乎很多人遇 ...
- Spring MVC 实现Excel的导入导出功能(1:Excel的导入)
简介 这篇文章主要记录自己学习上传和导出Excel时的一些心得,企业办公系统的开发中,经常会收到这样的需求:批量录入数据.数据报表使用 Excel 打开,或者职能部门同事要打印 Excel 文件,而他 ...
- Java基础(3)——变量
从这篇文章起开始正式进入正题啦,本文将较为简单的介绍一下变量以及常量.变量,顾名思义,就是可以变的量,常量那么久相反了,常常不变的量就叫常量._(¦3」∠) 变量 在 Java 中,任何一个变量都得有 ...
- XML修改节点值
基于DOM4J 先获取根节点 doc.getRootElement() 然后获取需要修改的节点 doc.getRootElement().node(int) 重新赋值 doc.getRootEleme ...
- Spring的自动装配Bean
spring的自动装配功能的定义:无须在Spring配置文件中描述javaBean之间的依赖关系(如配置<property>.<constructor-arg>).IOC容器会 ...
- "Error: ANDROID_HOME is not set and "android" command not in your PATH. You must fulfill at least one of these conditions.".
设置环境变量 set ANDROID_HOME=C:\\android-sdk-windows set PATH=%PATH%;%ANDROID_HOME%\tools;%ANDROID_HOME%\ ...
- BZOJ1968 [Ahoi2005] 约数研究
Description Input 只有一行一个整数 N(0 < N < 1000000). Output 只有一行输出,为整数M,即f(1)到f(N)的累加和. Sample Input ...