Android 對(duì)象池實(shí)現(xiàn)原理和簡(jiǎn)單使用

了解Android 垃圾回收

判斷對(duì)象是否可以被回收
垃圾收集算法
內(nèi)存分配與回收策略

GC頻繁原因

Memory Churn內(nèi)存抖動(dòng)冬阳,內(nèi)存抖動(dòng)是因?yàn)榇罅康膶?duì)象被創(chuàng)建又在短時(shí)間內(nèi)馬上被釋放蹂随。瞬間產(chǎn)生大量的對(duì)象會(huì)嚴(yán)重占用Young Generation的內(nèi)存區(qū)域,當(dāng)達(dá)到閥值滨砍,剩余空間不夠的時(shí)候,就會(huì)觸發(fā)GC。即使每次分配的對(duì)象占用了很少的內(nèi)存瘩例,但是他們疊加在一起會(huì)增加Heap的壓力,從而觸發(fā)更多其他類型的GC甸各。

對(duì)象復(fù)用

如果我們發(fā)現(xiàn)了大量臨時(shí)對(duì)象的創(chuàng)建該如何處理呢垛贤?

首先確定問(wèn)題發(fā)生的原因,對(duì)象頻繁創(chuàng)建一般有以下幾種可能:

  • 在循環(huán)操作中創(chuàng)建對(duì)象
  • 在頻繁被調(diào)用的方法中創(chuàng)建對(duì)象

例如:在onDraw中使用一些對(duì)象趣倾,由于onDraw方法會(huì)被程序頻繁調(diào)用聘惦,所以我們不能在onDraw方法里面創(chuàng)建對(duì)象實(shí)例,我們可以考慮在onDraw方法外提前初始化這些對(duì)象儒恋。

能直接避免對(duì)象的頻繁創(chuàng)建當(dāng)然最好善绎,但是有時(shí)候這些對(duì)象的初始化是不可避免的,那么我們就要考慮對(duì)象的復(fù)用诫尽,采用對(duì)象池來(lái)解決問(wèn)題禀酱。

對(duì)象池工作過(guò)程

圖解
  • Pool 表示的是對(duì)象池,用于存儲(chǔ)可重復(fù)利用的對(duì)象
  • 第二步操作就是取出對(duì)象牧嫉。
  • 第三步操作是使用取出的對(duì)象來(lái)完成一些任務(wù)剂跟。這時(shí)候并不需要對(duì)象池再做什么,但是這也意味著該對(duì)象將被租借一段時(shí)間并且不能在被其他組件借出酣藻。
  • 第四步操作就是歸還曹洽,組件歸還借出的對(duì)象這樣可以繼續(xù)滿足其他的租借請(qǐng)求。

在一個(gè)多線程的應(yīng)用中臊恋,第二衣洁,第三,第四步操作都有可能發(fā)生并發(fā)操作抖仅。多線程的組件中分享對(duì)象導(dǎo)致了潛在的并發(fā)問(wèn)題坊夫。也存在一種情況就是當(dāng)所有對(duì)象都被借出時(shí)不能滿足接下來(lái)的請(qǐng)求,對(duì)象池必須應(yīng)對(duì)這些請(qǐng)求撤卢,不管是告訴組件已經(jīng)沒(méi)有對(duì)象可借還是允許組件等待直到有歸還的對(duì)象环凿。

Android官方對(duì)象池源碼解析(見(jiàn)注釋說(shuō)明)

public final class Pools {

    public interface Pool<T> {

        /**
         * @return 從對(duì)象池取出對(duì)象
         */
        @Nullable
        T acquire();

        /**
         * 釋放對(duì)象并放入對(duì)象池
         *  
         * @return true表示釋放的對(duì)象成功放入對(duì)象池
         *
         * @throws IllegalStateException 如果對(duì)象已經(jīng)存在于對(duì)象池中拋異常
         */
        boolean release(@NonNull T instance);
    }

    private Pools() {
        /* do nothing - hiding constructor */
    }

    /**
     * 對(duì)象池的非同步實(shí)現(xiàn)
     */
    public static class SimplePool<T> implements Pool<T> {
        private final Object[] mPool;  //對(duì)象池中真正用于存儲(chǔ)對(duì)象的數(shù)組

        private int mPoolSize;  //對(duì)象池內(nèi)的對(duì)象個(gè)數(shù)

        /**
         * Creates a new instance.
         *
         * @param maxPoolSize 對(duì)象池最大容量
         */
        public SimplePool(int maxPoolSize) {
            if (maxPoolSize <= 0) {
                throw new IllegalArgumentException("The max pool size must be > 0");
            }
            mPool = new Object[maxPoolSize];  //初始化對(duì)象數(shù)組
        }

         //從mPool數(shù)組中取出mPoolSize - 1位置上的對(duì)象
        @Override
        @SuppressWarnings("unchecked")
        public T acquire() {
            if (mPoolSize > 0) {
                final int lastPooledIndex = mPoolSize - 1;
                T instance = (T) mPool[lastPooledIndex];
                mPool[lastPooledIndex] = null;
                mPoolSize--;
                return instance;
            }
            return null;
        }

        //回收的對(duì)象放入mPool數(shù)組的mPoolSize 位置上
        @Override
        public boolean release(@NonNull T instance) {
            if (isInPool(instance)) {
                throw new IllegalStateException("Already in the pool!");
            }
            if (mPoolSize < mPool.length) {
                mPool[mPoolSize] = instance;
                mPoolSize++;
                return true;
            }
            return false;
        }

        //判斷對(duì)象是否已存在于對(duì)象池中
        private boolean isInPool(@NonNull T instance) {
            for (int i = 0; i < mPoolSize; i++) {
                if (mPool[i] == instance) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * 對(duì)象池的同步實(shí)現(xiàn)
     *
     * @param <T> The pooled type.
     */
    public static class SynchronizedPool<T> extends SimplePool<T> {
        private final Object mLock = new Object();  //用于同步加鎖的對(duì)象

        /**
         * Creates a new instance.
         *
         * @param maxPoolSize The max pool size.
         *
         * @throws IllegalArgumentException If the max pool size is less than zero.
         */
        public SynchronizedPool(int maxPoolSize) {
            super(maxPoolSize);
        }

        @Override
        public T acquire() {
            synchronized (mLock) {
                return super.acquire();
            }
        }

        @Override
        public boolean release(@NonNull T element) {
            synchronized (mLock) {
                return super.release(element);
            }
        }
    }
}

優(yōu)點(diǎn)

  • 復(fù)用對(duì)象池中的對(duì)象,可以避免頻繁創(chuàng)建和銷毀堆中的對(duì)放吩, 進(jìn)而減少垃圾收集器的負(fù)擔(dān)智听, 減少內(nèi)存抖動(dòng);
  • 不必重復(fù)初始化對(duì)象狀態(tài), 對(duì)于比較耗時(shí)的constructor和finalize來(lái)說(shuō)非常合適到推;

缺點(diǎn)

  • Java的對(duì)象分配操作不比c語(yǔ)言的malloc調(diào)用慢考赛, 對(duì)于輕中量級(jí)的對(duì)象, 分配/釋放對(duì)象的開(kāi)銷可以忽略不計(jì)莉测;
  • 并發(fā)環(huán)境中颜骤, 多個(gè)線程可能(同時(shí))需要獲取池中對(duì)象, 進(jìn)而需要在堆數(shù)據(jù)結(jié)構(gòu)上進(jìn)行同步或者因?yàn)殒i競(jìng)爭(zhēng)而產(chǎn)生阻塞捣卤, 這種開(kāi)銷要比創(chuàng)建銷毀對(duì)象的開(kāi)銷高數(shù)百倍忍抽;
  • 由于池中對(duì)象的數(shù)量有限, 勢(shì)必成為一個(gè)可伸縮性瓶頸董朝;
  • 很難正確的設(shè)定對(duì)象池的大小鸠项, 如果太小則起不到作用, 如果過(guò)大子姜, 則占用內(nèi)存資源高祟绊, 可以開(kāi)啟一個(gè)線程定期掃描分析, 將對(duì)象池壓縮到一個(gè)合適的尺寸以節(jié)約內(nèi)存哥捕,但為了獲得不錯(cuò)的分析結(jié)果久免, 在掃描期間可能需要暫停復(fù)用以避免干擾(造成效率低下), 或者使用非常復(fù)雜的算法策略(增加維護(hù)難度)扭弧;
  • 設(shè)計(jì)和使用對(duì)象池容易出錯(cuò)阎姥, 設(shè)計(jì)上需要注意狀態(tài)同步, 這是個(gè)難點(diǎn)鸽捻, 使用上可能存在忘記歸還(就像c語(yǔ)言編程忘記free一樣)呼巴, 重復(fù)歸還(可能需要做個(gè)循環(huán)判斷一下是否池中存在此對(duì)象, 這也是個(gè)開(kāi)銷)御蒲, 歸還后仍舊使用對(duì)象(可能造成多個(gè)線程并發(fā)使用一個(gè)對(duì)象的情況)等問(wèn)題衣赶。

對(duì)象池的使用

在源碼中摘取出來(lái)對(duì)象池的使用幫助類:

/**
 * Helper class for creating pools of objects. An example use looks like this:
 * 
 * public class MyPooledClass {
 *
 *     private static final SynchronizedPool<MyPooledClass> sPool =
 *             new SynchronizedPool<MyPooledClass>(10);
 *
 *     public static MyPooledClass obtain() {
 *         MyPooledClass instance = sPool.acquire();
 *         return (instance != null) ? instance : new MyPooledClass();
 *     }
 *
 *     public void recycle() {
 *          // Clear state if needed.
 *          sPool.release(this);
 *     }
 *
 * }
 * /

參考

https://www.dazhuanlan.com/2019/12/24/5e02022da1e64/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市厚满,隨后出現(xiàn)的幾起案子府瞄,更是在濱河造成了極大的恐慌,老刑警劉巖碘箍,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遵馆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡丰榴,警方通過(guò)查閱死者的電腦和手機(jī)货邓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)四濒,“玉大人换况,你說(shuō)我怎么就攤上這事职辨。” “怎么了戈二?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵舒裤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我觉吭,道長(zhǎng)惭每,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任亏栈,我火速辦了婚禮,結(jié)果婚禮上宏赘,老公的妹妹穿的比我還像新娘绒北。我一直安慰自己,他們只是感情好察署,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布闷游。 她就那樣靜靜地躺著,像睡著了一般贴汪。 火紅的嫁衣襯著肌膚如雪脐往。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天扳埂,我揣著相機(jī)與錄音业簿,去河邊找鬼。 笑死阳懂,一個(gè)胖子當(dāng)著我的面吹牛梅尤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播岩调,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼巷燥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了号枕?” 一聲冷哼從身側(cè)響起缰揪,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎葱淳,沒(méi)想到半個(gè)月后钝腺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赞厕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年拍屑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坑傅。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡僵驰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蒜茴,我是刑警寧澤星爪,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站粉私,受9級(jí)特大地震影響顽腾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诺核,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一抄肖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧窖杀,春花似錦漓摩、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至桌硫,卻和暖如春夭咬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铆隘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工卓舵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人膀钠。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓边器,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親托修。 傳聞我的和親對(duì)象是個(gè)殘疾皇子忘巧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345