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 &nbsp; &nbsp;( 2889): Start testWeakClass ..
D/TEST &nbsp; &nbsp;( 2889): Construct: testWeakClassA
D/TEST &nbsp; &nbsp;( 2889): Construct: testWeakClassB
D/TEST &nbsp; &nbsp;( 2889): destruct: testWeakClassB
D/TEST &nbsp; &nbsp;( 2889): destruct: testWeakClassA
D/TEST &nbsp; &nbsp;( 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指针引用。

建议

没什么好建议,搞明白设计意图和工作原理对调试和写代码都非常有好处。

转载:http://blog.csdn.net/freshui/article/details/9049193#

时间: 2024-10-11 21:31:01

Android的引用计数(强弱指针)技术及一些问题的相关文章

引用计数智能指针

<a>C++ <span style="font-family:宋体;">智能指针具体解释</span></a> 一.简单介绍 因为 C++ 语言没有自己主动内存回收机制.程序猿每次 new 出来的内存都要手动 delete. 程序猿忘记 delete.流程太复杂.终于导致没有 delete.异常导致程序过早退出,没有运行 delete 的情况并不罕见. 用智能指针便能够有效缓解这类问题,本文主要解说參见的智能指针的使用方法.包含:std

C++引用计数智能指针

#include <iostream> using namespace std; template<typename Type> class auto_ptr_ { public: auto_ptr_(Type *t = NULL) :ptr(t), count(new int(1)) { } auto_ptr_(const auto_ptr_& at) :ptr(at.ptr), count(at.count)//先将count指向at的count. { count[0]

引用计数

在引用计数中,每一个对象负责维护对象所有引用的计数值.当一个新的引用指向对象时,引用计数器就递增,当去掉一个引用时,引用计数就递减.当引用计数到零时,该对象就将释放占有的资源.中文名引用计数原 因程序调试原 理每一个对象负责维护对象所有引用的计数值类 型最直观的垃圾收集策略目录1简介2引用计数的使用? 原因? 规则? 接口? 调试? 优化? 规则1简介编辑 最直观的垃圾收集策略是引用计数.引用计数很简单,但是需要编译器的重要配合,并且增加了赋值函数 (mutator) 的开销(这个术语是针对用户

Cocos2d之&ldquo;引用计数&rdquo;内存管理机制实现解析

一.引言 本文主要分析cocos2d游戏开发引擎的引用计数内存管理技术的实现原理.建议读者在阅读本文之前阅读笔者之前一篇介绍如何使用cocos2d内存管理技术的文章--<Cocos2d之Ref类与内存管理使用详解>. 二.相关概念 引用计数 引用计数是计算机编程语言的一种内存管理技术,是指将资源(对象.内存或者磁盘空间等)的被引用计数保存起来,当引用计数变为零时就将资源释放的过程.使用引用计数技术可以实现自动内存管理的目的. 当实例化一个类时,对象的引用计数为1,在其他对象需要持有这个对象时,

动态内存管理(引用计数)

c++的动态内存管理是非常重要的,操作不当很容易引起内存泄漏, 下面我详细写了一些内存管理该注意的地方,包括引用计数的实现 深拷贝浅拷贝 #include <iostream>using namespace std; class String{public: String()  :_str(new char[1]) {  *_str = '\0'; } String(char* str)  :_str(new char[strlen(str)+1])  //开辟一段新空间给_str {  st

智能指针的实现--使用引用计数实现以及原理

一.智能指针 在C++语言编程时,当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝:另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享. 智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count).智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针. 每次创建类的新对象时,初始化指针并将引用计数置为1:当对象作为另一对象的副本而创建时,拷贝

基于引用计数的智能指针

编程语言中实现自动垃圾回收机制方式有好几种,常见的有标记清除,引用计数,分代回收等. C++需要手动管理垃圾,可以自己实现一个智能指针.最简单的是引用计数的思路 template <class T> class SmartPointer { T* obj; unsigned int* count; SmartPointer(T* ptr) { obj = ptr; count = new int; *count = 1; } SmartPointer(SmartPointer &p)

引用计数指针

如前面内存泄露中所讲的,引用计数指针可以被复制.因此,一个智能指针的几份拷贝可以指向同一个对象.这就产生了由哪份拷贝负责删除它们共同指向的对象这个问题.答案是这组智能指针中最后消亡的那个将删除它所指向的对象.这类似于家居规则:"最后一个离开屋子的人负责关灯."为了实现这个算法,这些指针共享一个计数器,记录有多少个智能指针引用同一个对象,即"引用计数"这个术语的由来.引用计数的应用范围很广:这个术语简单意味着,编译器具有一个隐藏的整型变量作为计数器.每当有人创建了一个

引用计数的智能指针的实现

引用计数的智能指针是对<Effective C++ 条款13:以对象管理资源>的一个实现. 我们要设计一个智能指针,使他能够管理资源,在正确的实际调用资源的析构函数. 首先我们需要一个指针reference来指向资源,当智能指针构造时,用reference指针指向资源,在我们确定资源应该被析构时,我们对reference指针进行delete. 如果只有reference指针的话,只能实现出auto_ptr的效果,我们还需要添加引用计数系统counter来统计指向资源的智能指针的个数.count