淺談 Recycle 機(jī)制

這里的 Recycle 機(jī)制并不是指 Java 虛擬機(jī)中的垃圾回收機(jī)制,而是 Android 框架里十分常用的一種設(shè)計(jì)模式?;舅枷牒芎?jiǎn)單,當(dāng)一個(gè)對(duì)象不再使用時(shí)把它儲(chǔ)藏起來,不讓虛擬機(jī)回收,需要的時(shí)候再從倉庫里拿出來重新使用,這就避免了對(duì)象被回收后再重分配的過程。對(duì)于在應(yīng)用的生命周期內(nèi)(或者在循環(huán)中)需要頻繁創(chuàng)建的對(duì)象來說這個(gè)機(jī)制特別實(shí)用,可以顯著減少對(duì)象創(chuàng)建的次數(shù),從而減少 GC 的運(yùn)行時(shí)間。運(yùn)用得當(dāng)便可改善應(yīng)用的性能。唯一的不足只是需要手動(dòng)為廢棄對(duì)象調(diào)用 recycle 方法。

如何實(shí)現(xiàn)?首先,我們需要一個(gè)倉庫用于存放暫時(shí)不用的對(duì)象;需要新對(duì)象的時(shí)候我們不能使用 new 來分配一個(gè)新對(duì)象,所以還需要一個(gè)方法 obtain 來從倉庫里獲取對(duì)象;最后,便是 recycle 方法了,用于回收不再使用的對(duì)象。一個(gè)簡(jiǎn)單的實(shí)現(xiàn)如下所示,技術(shù)細(xì)節(jié)在注釋里解釋:

/**
 * Created by Tiou on 2014/7/15.
 * 一個(gè)實(shí)現(xiàn) Recycle 機(jī)制的對(duì)象
 */
public class Data {
    /**
     * 對(duì)象池,就是上文所提到的對(duì)象倉庫,用于暫時(shí)存放不用的對(duì)象。
     * 用鏈表來實(shí)現(xiàn)對(duì)象池結(jié)構(gòu),直觀,高效,易用。
     * sPool 便是指向鏈表頭部的引用
     */
    private static Data sPool;
    /**
     * 指向鏈表中的下一個(gè)元素,當(dāng) next 為 null 時(shí)表示已到達(dá)鏈表末端
     */
    private Data next;

    /**
     * 隱藏構(gòu)造函數(shù),避免對(duì)象被 new 關(guān)鍵字創(chuàng)建
     */
    private Data(){}

    /**
     * 從池里獲取一個(gè)新對(duì)象,沒有的話則返回一個(gè)新的實(shí)例
     * @return 可用的新對(duì)象
     */
    public static Data obtain(){
        if(sPool!=null){ // 池中有可用的對(duì)象
            // 對(duì)于對(duì)象池來說順序并沒有關(guān)系
            // 這里取鏈表的第一個(gè)對(duì)象,主要是因?yàn)榉奖?            Data data = sPool;
            sPool = sPool.next;
            data.next = null;
            return data;
        }
        return new Data();
    }

    /**
     * 將當(dāng)前對(duì)象回收,一旦對(duì)象被回收,便不能再使用,代碼中也不應(yīng)存有任何到該對(duì)象的引用
     */
    public void recycle(){
        clear(); //清理對(duì)象
        // 把當(dāng)前對(duì)象作為首元素按入鏈表中
        next = sPool;
        sPool = this;
    }

    /**
     * 重置對(duì)象到剛初始化時(shí)的狀態(tài)
     */
    private void clear(){

    }
}

為什么說這是一個(gè)簡(jiǎn)單實(shí)現(xiàn)呢?因?yàn)檫@是一個(gè)不完善的實(shí)現(xiàn)。考慮一個(gè)場(chǎng)景,如果一次性 obtain 十萬個(gè)對(duì)象,用完后再全部 recycle,以后每次可能規(guī)模就降到幾十個(gè),那這十萬個(gè)對(duì)象的絕大多數(shù)就會(huì)停留在池里,既不會(huì)被用到,也不能被虛擬機(jī)回收,占用應(yīng)用大量的內(nèi)存。這是個(gè)十分糟糕的例子,但意思大致還是說得明白,池的容量不能無限大,不然便有內(nèi)存泄漏的隱患。至于這個(gè)對(duì)象數(shù)量的上限該如何設(shè)置,這里并沒有一個(gè)規(guī)定死的值,可靈活設(shè)置,簡(jiǎn)單說這是一個(gè)空間換時(shí)間的策略,可根據(jù)對(duì)象占用的空間,及應(yīng)用具體需要用到的規(guī)模來設(shè)置一個(gè)合理值。

另外,obtainrecycle 這兩個(gè)方法都不是原子操作,在多線程的應(yīng)用場(chǎng)景下,可能會(huì)發(fā)生各種奇怪的問題。所以我們還要為這兩個(gè)方法加鎖,保證其是多線程安全的。

最終的效果在這個(gè) gist.

至于具體的例子,這個(gè)機(jī)制在 Android 框架中實(shí)在是太常見了,都不用自己再造出什么例子。只要用過 Message, TypedArray, Parcel,甚至各種 Event 類,等等…都或多或少接觸過 recycle 這個(gè)方法。這個(gè)機(jī)制如此常用,以至于 Android 還在 support lib v4 里提供一個(gè) Pool 工具包。

大家可能會(huì)奇怪了:「我常常用 Message,也沒調(diào)用過recycle,也不見得會(huì)怎樣?!箤?shí)際上不調(diào)用 recycle 確實(shí)不會(huì)在怎樣,因?yàn)?Looper 已經(jīng)幫我們處理好手尾了,只要記得發(fā)送過的 Message 不能再用便可以。那自己手動(dòng)回收會(huì)怎樣?因?yàn)?Looper 在調(diào)用 msg.recycle() 前并沒有作檢查,Message 的對(duì)象池來者不拒,不會(huì)對(duì)進(jìn)入池中的對(duì)象是否已存在進(jìn)行檢查,一旦同一個(gè) Message 被回收兩次,便會(huì)發(fā)生糟糕的結(jié)果,對(duì)象池將會(huì)被破壞,變成一條循環(huán)鏈表,該 Message 所在節(jié)點(diǎn)后面的元素將會(huì)被孤立,雖不會(huì)造成內(nèi)存泄漏,但將影響虛擬機(jī)回收的回收效率。更糟糕的是,Message 的 Recycle 機(jī)制將會(huì)失效。

大家可以試試下面的代碼:

Message msg = Message.obtain();
System.out.println("First obtain:"+System.identityHashCode(msg));
msg.recycle();
msg.recycle();
System.out.println("After recycle twice");
System.out.println("Second obtain:"+System.identityHashCode(Message.obtain()));
System.out.println("Third obtain:"+System.identityHashCode(Message.obtain()));
System.out.println("Fourth obtain:"+System.identityHashCode(Message.obtain()));

輸出結(jié)果:

First obtain:1122593040
After recycle twice
Second obtain:1122593040
Third obtain:1122593040
Fourth obtain:1122846456

可以看到,連續(xù)兩次 obtain 返回相同的對(duì)象,一旦出現(xiàn)這樣的 Bug,要找問題在哪出來絕對(duì)是很困難的,所以,絕對(duì)不要手動(dòng)調(diào)用 Message#recycle. 不得不懷疑 Android 把這個(gè)方法聲明為 public 是否合理的。

順便再提一下,Event 類的回收機(jī)制也是由系統(tǒng)控制的,所以不要在監(jiān)聽器觸發(fā)的方法外保留對(duì)監(jiān)聽事件的引用。

本文依據(jù) Android Programming - Pushing the Limits 而作,該書同樣犯了手動(dòng)調(diào)用 Message#recycle 的錯(cuò)誤,但仍不失為一本值得一讀的技術(shù)書,值得推薦。

原文鏈接:https://dourok.info/2014/07/15/quick-overview-of-recycling-pattern-in-android/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容