Android-Bitmap 回收機制

包括 recycle() 方法 bitmap 回收時機峡懈。

手動調用recycle()

2.3 及以下版本好渠,保存在 jvm + native 中,手動調用 recycle() 彤恶。
7.0 和 8.0 不需要調用 recycle()袋倔,下面是該版本下代碼。


    /**
     * Free the native object associated with this bitmap, and clear the
     * reference to the pixel data. This will not free the pixel data synchronously;
     * it simply allows it to be garbage collected if there are no other references.
     * The bitmap is marked as "dead", meaning it will throw an exception if
     * getPixels() or setPixels() is called, and will draw nothing. This operation
     * cannot be reversed, so it should only be called if you are sure there are no
     * further uses for the bitmap. This is an advanced call, and normally need
     * not be called, since the normal GC process will free up this memory when
     * there are no more references to this bitmap.
     */
    //釋放與此位圖關聯(lián)的本機對象诅岩,并清除對像素數(shù)據(jù)的引用讳苦。 這不會同步釋放像素數(shù)據(jù);
    //這是一個高級調用吩谦,通常不需要調用鸳谜,因為當沒有更多對此位圖的引用時,正常的 GC 過程將釋放此內存式廷。
    public void recycle() {
        if (!mRecycled) {
            nativeRecycle(mNativePtr);
            mNinePatchChunk = null;
            mRecycled = true;
        }
    }
  
    private static native void nativeRecycle(long nativeBitmap);

7.0 跟 8.0 nativeRecycle 實現(xiàn)不同咐扭。

8.0 版本 Bitmap

static jboolean Bitmap_recycle(JNIEnv* env, jobject, jlong bitmapHandle) {
    LocalScopedBitmap bitmap(bitmapHandle);
    bitmap->freePixels();
    return JNI_TRUE;
}

void freePixels() {
    mInfo = mBitmap->info();
    mHasHardwareMipMap = mBitmap->hasHardwareMipMap();
    mAllocationSize = mBitmap->getAllocationByteCount();
    mRowBytes = mBitmap->rowBytes();
    mGenerationId = mBitmap->getGenerationID();
    mIsHardware = mBitmap->isHardware();
    //清空數(shù)據(jù)
    mBitmap.reset();
}

7.0 版本 Bitmap

static jboolean Bitmap_recycle(JNIEnv *env, jobject, jlong bitmapHandle) {
    LocalScopedBitmap bitmap(bitmapHandle);
    bitmap->freePixels();
    return JNI_TRUE;
}
//析構函數(shù)
Bitmap::~Bitmap() {
    doFreePixels();
}

void Bitmap::doFreePixels() {
    switch (mPixelStorageType) {
        //Invalid表示圖片已經(jīng)失效了,一般圖片free掉之后就會是這種狀態(tài)
        case PixelStorageType::Invalid:
            break;
        //外部存儲
        case PixelStorageType::External:
            mPixelStorage.external.freeFunc(mPixelStorage.external.address,
                mPixelStorage.external.context);
        break;
        //匿名共享內存滑废,F(xiàn)resco 低版本內存優(yōu)化蝗肪,通過 BitmapFactory.Options.inPurgeable = true 
        //使得 bitmap 通過匿名共享內存方式保存
        case PixelStorageType::Ashmem:
            munmap(mPixelStorage.ashmem.address, mPixelStorage.ashmem.size);
            close(mPixelStorage.ashmem.fd);
            break;
        //java內存,主要看這種
        case PixelStorageType::Java:
            JNIEnv* env = jniEnv();
            LOG_ALWAYS_FATAL_IF(mPixelStorage.java.jstrongRef,
                "Deleting a bitmap wrapper while there are outstanding strong "
                "references! mPinnedRefCount = %d", mPinnedRefCount);
        //DeleteWeakGlobalRef 該函數(shù)刪除一個弱全局引用蠕趁,添加待刪除的弱全局引用給 jvm
        env->DeleteWeakGlobalRef(mPixelStorage.java.jweakRef);
            break;
        }

        if (android::uirenderer::Caches::hasInstance()) {
            android::uirenderer::Caches::getInstance().textureCache.releaseTexture(
                mPixelRef->getStableID());
    }
}
  1. 8.0 中薛闪,手動調用 recycle() 方法,像素數(shù)據(jù)會立即釋放俺陋;

  2. 7.0 版本中豁延,調用 recycle() 方法像素數(shù)據(jù)不會立即釋放怀各,而是通過 DeleteWeakGlobalRef 交由 Jvm GC 處理。Java 層主動調用 recycle() 或者在 Bitmap 析構函數(shù) freePixels() 术浪,移除 Global 對象引用瓢对,這個對象是 Heap 內存一堆像素的空間。GC 時釋放掉胰苏。二是 JNI 不再持有 Global Reference硕蛹,并 native 函數(shù)執(zhí)行后釋放掉,但 Java 層的 Bitmap 對象還在硕并,只是它的 mBuffer 和 mNativePtr 是無效地址法焰,沒有像素 Heap 的 Bitmap 也就幾乎不消耗內存。Java 層 Bitmap 對象什么時候釋放倔毙,生命周期結束自然會 free 掉埃仪。

回收機制

7.0 通過 java 層 BitmapFinalizer.finalize 實現(xiàn),8.0 通過 native 層 NativeAllocationRegistry 實現(xiàn)陕赃。

7.0 及以下卵蛉,保存在 jvm 虛擬機中,回收機制通過 BitmapFinalizer. finalize 實現(xiàn)么库。跟 BitmapFinalizer 而不跟 Bitmap 關聯(lián)的原因是重寫 finalize 方法的對象會被延遲回收傻丝,經(jīng)過兩次 gc 才會被回收。原因是重寫 finalize 的對象在創(chuàng)建時會創(chuàng)建 FinalizerReference 并引用 Object诉儒,并將FR 關聯(lián)到 ReferenceQueue 中葡缰,當?shù)谝淮螆?zhí)行 gc 時候,會把 FR 放在 queue 中忱反,其中有個守護線程持續(xù)讀取 queue 中的數(shù)據(jù)泛释,并執(zhí)行 finalize 方法。

Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
            boolean isMutable, boolean requestPremultiplied,
            byte[] ninePatchChunk, NinePatchInsetStruct ninePatchInsets) {
        if (nativeBitmap == 0) {
            throw new RuntimeException("internal error: native bitmap is 0");
        }

        ...
        mNativePtr = nativeBitmap;
        // 這個對象對象來回收
        mFinalizer = new BitmapFinalizer(nativeBitmap);
        int nativeAllocationByteCount = (buffer == null ? getByteCount() : 0);
        mFinalizer.setNativeAllocationByteCount(nativeAllocationByteCount);
    }

    private static class BitmapFinalizer {
        private long mNativeBitmap;

        // Native memory allocated for the duration of the Bitmap,
        // if pixel data allocated into native memory, instead of java byte[]
        private int mNativeAllocationByteCount;

        BitmapFinalizer(long nativeBitmap) {
            mNativeBitmap = nativeBitmap;
        }

        public void setNativeAllocationByteCount(int nativeByteCount) {
            if (mNativeAllocationByteCount != 0) {
                //注銷 native 內存
                VMRuntime.getRuntime().registerNativeFree(mNativeAllocationByteCount);
            }
            mNativeAllocationByteCount = nativeByteCount;
            if (mNativeAllocationByteCount != 0) {
                VMRuntime.getRuntime().registerNativeAllocation(mNativeAllocationByteCount);
            }
        }

        @Override
        public void finalize() {
            try {
                super.finalize();
            } catch (Throwable t) {
                // Ignore
            } finally {
                // finalize 這里是 GC 回收該對象時會調用
                setNativeAllocationByteCount(0);
                //本地析構函數(shù)温算,釋放資源
                nativeDestructor(mNativeBitmap);
                mNativeBitmap = 0;
            }
        }
    }

    private static native void nativeDestructor(long nativeBitmap);

8.0 以上怜校,保存在 jvm + native 中,NativeAllocationRegistry 中把 native 的文件描述符(句柄)跟 Cleaner 關聯(lián)米者,Cleaner 其實是個虛應用韭畸,會在沒有強應用關聯(lián)的時候進行內存回收宇智。

    // called from JNI
    Bitmap(long nativeBitmap, int width, int height, int density,
            boolean isMutable, boolean requestPremultiplied,
            byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
        ...

        mNativePtr = nativeBitmap;
        long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
        //nativeGetNativeFinalizer() 方法傳入 NativeAllocationRegistry
        NativeAllocationRegistry registry = new NativeAllocationRegistry(
                Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
        //注冊 Java 層對象引用與 native 層對象的地址
        registry.registerNativeAllocation(this, nativeBitmap);
    }

下面是 NativeAllocationRegistry.java 代碼

public class NativeAllocationRegistry {

        private final ClassLoader classLoader;
        private final long freeFunction;
        private final long size;

        public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
            ...
            //外部傳入的 nativeGetNativeFinalizer 蔓搞,有用
            this.freeFunction = freeFunction;
            this.size = size;
        }

        public Runnable registerNativeAllocation(Object referent, long nativePtr) {
            ...

            try {
                //注冊 native 內存
                registerNativeAllocation(this.size);
            } catch (OutOfMemoryError oome) {
                applyFreeFunction(freeFunction, nativePtr);
                throw oome;
            }
            // Cleaner 綁定 Java 對象與回收函數(shù)
            Cleaner cleaner = Cleaner.create(referent, new CleanerThunk(nativePtr));
            return new CleanerRunner(cleaner);
        }


        private class CleanerThunk implements Runnable {
            private long nativePtr;

            public CleanerThunk(long nativePtr) {
                this.nativePtr = nativePtr;
            }

            public void run() {
                if (nativePtr != 0) {
                    //freeFunction 即為 nativeGetNativeFinalizer() 方法
                    applyFreeFunction(freeFunction, nativePtr);
                }
                registerNativeFree(size);
            }

        }

        private static class CleanerRunner implements Runnable {
            private final Cleaner cleaner;

            public CleanerRunner(Cleaner cleaner) {
                this.cleaner = cleaner;
            }

            public void run() {
                cleaner.clean();
            }
        }

        public static native void applyFreeFunction(long freeFunction, long nativePtr);
    }

NativeAllocationRegistry 內部是利用了 sun.misc.Cleaner.java 機制,簡單來說:使用虛引用得知對象被GC的時機随橘,在GC前執(zhí)行額外的回收工作喂分。

nativeGetNativeFinalizer()
// Bitmap.java
private static native long nativeGetNativeFinalizer();

// Bitmap.cpp
static jlong Bitmap_getNativeFinalizer(JNIEnv*, jobject) {
    // 轉為long
    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Bitmap_destruct));
}

static void Bitmap_destruct(BitmapWrapper* bitmap) {
    delete bitmap;
}

nativeGetNativeFinalizer 是一個 native 函數(shù),返回值是一個 long机蔗,這個值其實相當于 Bitmap_destruct() 函數(shù)的直接地址蒲祈,Bitmap_destruct() 就是用來回收 native 層內存的甘萧。

故 NativeAllocationRegistry 利用虛引用感知 Java 對象被回收的時機,來回收 native 層內存梆掸;在 Android 8.0 (API 27) 之前扬卷,Android 通常使用Object#finalize() 調用時機來回收 native 層內存

Tips:Fresco 在 Android 4.4 及以下版本對 bitmap 內存做了優(yōu)化,通過設置 BitmapFactory.Options.inPurgeable = true 使得 bitmap 保存在匿名共享內存中酸钦,減少了 Jvm 虛擬機內存占用怪得。

參考:
最詳細的Android Bitmap回收機制(從2.3到7.0,8.0)
避免使用finalize()方法
Android | 帶你理解 NativeAllocationRegistry 的原理與設計思想
ART虛擬機 | Finalize的替代者Cleaner
引入Fresco前的調研報告
理解Android Bitmap
Android | 帶你理解 NativeAllocationRegistry 的原理與設計思想

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末卑硫,一起剝皮案震驚了整個濱河市徒恋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌欢伏,老刑警劉巖入挣,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異硝拧,居然都是意外死亡径筏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進店門障陶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匠璧,“玉大人,你說我怎么就攤上這事咸这∫幕校” “怎么了?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵媳维,是天一觀的道長酿雪。 經(jīng)常有香客問我,道長侄刽,這世上最難降的妖魔是什么指黎? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮州丹,結果婚禮上醋安,老公的妹妹穿的比我還像新娘。我一直安慰自己墓毒,他們只是感情好吓揪,可當我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著所计,像睡著了一般柠辞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上主胧,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天叭首,我揣著相機與錄音习勤,去河邊找鬼。 笑死焙格,一個胖子當著我的面吹牛图毕,可吹牛的內容都是我干的。 我是一名探鬼主播眷唉,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼吴旋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了厢破?” 一聲冷哼從身側響起荣瑟,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎摩泪,沒想到半個月后笆焰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡见坑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年嚷掠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荞驴。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡不皆,死狀恐怖,靈堂內的尸體忽然破棺而出熊楼,到底是詐尸還是另有隱情霹娄,我是刑警寧澤,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布鲫骗,位于F島的核電站犬耻,受9級特大地震影響,放射性物質發(fā)生泄漏执泰。R本人自食惡果不足惜枕磁,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望术吝。 院中可真熱鬧计济,春花似錦、人聲如沸排苍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纪岁。三九已至凑队,卻和暖如春则果,著一層夾襖步出監(jiān)牢的瞬間幔翰,已是汗流浹背漩氨。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遗增,地道東北人叫惊。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像做修,于是被迫代替她去往敵國和親霍狰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,930評論 2 361

推薦閱讀更多精彩內容