Android的引用計數(強弱指標)技術及一些問題

來源:互聯網
上載者:User

標籤:des   android   style   blog   http   io   ar   color   os   

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: testStrongClassAD/TEST    ( 1552): Construct: testStrongClassBD/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: testWeakClassAD/TEST &nbsp; &nbsp;( 2889): Construct: testWeakClassBD/TEST &nbsp; &nbsp;( 2889): destruct: testWeakClassBD/TEST &nbsp; &nbsp;( 2889): destruct: testWeakClassAD/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: testNormalClassAD/TEST    ( 2889): Construct: testNormalClassBD/TEST    ( 2889): destruct: testNormalClassAD/TEST    ( 2889): destruct: testNormalClassBD/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: testCrashAD/TEST    ( 3709): destruct: testCrashAD/TEST    ( 3709): Construct: testCrashBD/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#

 

Android的引用計數(強弱指標)技術及一些問題

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.