最详细的Android Bitmap回收机制(从2.3到7.0,8.0)_nativeptr is null at libcore.util.nativeallocation-程序员宅基地

技术标签: Bitmap  版本迁移  内存回收  Android  

主要参照了这篇博客,https://www.jianshu.com/p/8e8ad414237e 但是原文写得不是很详细,做了些具体调用补充。

具体回收细节属于原创,原创不易,转载请注明出处https://blog.csdn.net/shihongyu12345/article/details/89681948,谢谢!

在 Android 2.3.3 之前开发者必须手动调用 recycle 方法去释放 Native 内存,因为那个时候管理Bitmap内存比较复杂,需要手动维护引用计数器,在官网上有如下一段解释:

代码块

On Android 2.3.3 (API level 10) and lower, using recycle() is recommended. If you're displaying large amounts of bitmap data in your app, you're likely to run into OutOfMemoryError errors. The recycle()method allows an app to reclaim memory as soon as possible.
Caution: You should use recycle() only when you are sure that the bitmap is no longer being used. If you call recycle() and later attempt to draw the bitmap, you will get the error: "Canvas: trying to use a recycled bitmap".
The following code snippet gives an example of calling recycle(). It uses reference counting (in the variables mDisplayRefCount and mCacheRefCount) to track whether a bitmap is currently being displayed or in the cache. The code recycles the bitmap when these conditions are met:
The reference count for both mDisplayRefCount and mCacheRefCount is 0.
The bitmap is not null, and it hasn't been recycled yet.

在 Android 2.3.3 以后不需要开发者主动调用 recycle 方法来回收内存了,但 Android K,L,M,N,O 版本上,都还能看到 recycle 方法,为什么没有干掉呢? 调用它会不会真正的释放内存呢?既然不需要手动释放 Native Bitmap ,那 Native 层的对象是怎么自动释放的?我们先来看下 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.
  */
  public void recycle() {
    if (!mRecycled && mNativePtr != 0) {
      if (nativeRecycle(mNativePtr)) {
        // return value indicates whether native pixel object was actually recycled.
        // false indicates that it is still in use at the native level and these
        // objects should not be collected now. They will be collected later when the
        // Bitmap itself is collected.
        mNinePatchChunk = null;
      }
      mRecycled = true;
    }
  }
​
  private static native boolean nativeRecycle(long nativeBitmap);

都是调用了native方法,下面看一下native方法

8.0 见:
/xref/frameworks/base/core/jni/android/graphics/Bitmap.cpp

代码块

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();
    // 清空了数据
    mBitmap.reset();
}

7.0 见:
/xref/frameworks/base/core/jni/android/graphics/Bitmap.cpp

代码块

static jboolean Bitmap_recycle(JNIEnv *env, jobject, jlong bitmapHandle) {
    LocalScopedBitmap bitmap(bitmapHandle);
    bitmap->freePixels();
    return JNI_TRUE;
}
void Bitmap::doFreePixels() {
    switch (mPixelStorageType) {
        case PixelStorageType::Invalid:
            // already free'd, nothing to do
            break;
        case PixelStorageType::External:
            mPixelStorage.external.freeFunc(mPixelStorage.external.address,
                                            183
            mPixelStorage.external.context);
            break;
        case PixelStorageType::Ashmem:
            munmap(mPixelStorage.ashmem.address, mPixelStorage.ashmem.size);
            close(mPixelStorage.ashmem.fd);
            break;
        case PixelStorageType::Java:
            // 只是释放了 Java 层之前创建的引用
            JNIEnv *env = jniEnv();
            LOG_ALWAYS_FATAL_IF(mPixelStorage.java.jstrongRef,
                                192
            "Deleting a bitmap wrapper while there are outstanding strong "
                    "references! mPinnedRefCount = %d", mPinnedRefCount);
            env->DeleteWeakGlobalRef(mPixelStorage.java.jweakRef);
            break;
    }
​
    if (android::uirenderer::Caches::hasInstance()) {
        android::uirenderer::Caches::getInstance().textureCache.releaseTexture(
                mPixelRef->getStableID());
    }
}

从上面的源码可以看出,如果是 8.0 我们手动调用 recycle 方法,数据是会立即释放的,因为像素数据本身就是在 Native 层开辟的。但如果是在 8.0 以下,就算我们手动调用 recycle 方法,数据也是不会立即释放的,而是 DeleteWeakGlobalRef 交由 Java GC 来回收。建议大家翻译一下 recycle 方法注释。注意:以上的所说的释放数据仅代表释放像素数据,并未释放 Native 层的 Bitmap 对象。

最后只剩下一个问题了,我们在开发的过程中一般情况下并不会手动去调用 recycle 方法,那 Native 层的 Bitmap 是怎么回收的呢?如果让我们来写这个代码,我们不妨思考一下该怎么下手?这里我就不卖关子了。在 new Bitmap 时,其实就已经指定了谁来控制 Bitmap 的内存回收。Android M 版本及以前的版本, Bitmap 的内存回收主要是通过 BitmapFinalizer 来完成的见:
/frameworks/base/graphics/java/android/graphics/Bitmap.java

代码块

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");
        }
​
        mWidth = width;
        mHeight = height;
        mIsMutable = isMutable;
        mRequestPremultiplied = requestPremultiplied;
        mBuffer = buffer;
​
        mNinePatchChunk = ninePatchChunk;
        mNinePatchInsets = ninePatchInsets;
        if (density >= 0) {
            mDensity = density;
        }
​
        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) {
                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);
                nativeDestructor(mNativeBitmap);
                mNativeBitmap = 0;
            }
        }
    }
​
    private static native void nativeDestructor(long nativeBitmap);

看到这里,可能有些人还是不知道怎么触发回收的。

这里要说一下libcore/libart/src/main/java/java/lang/Daemons.java中的FinalizerDaemon

FinalizerDaemon:析构守护线程。对于重写了成员函数finalize的对象,它们被GC决定回收时,并没有马上被回收,而是被放入到一个队列中,等待FinalizerDaemon守护线程去调用它们的成员函数finalize,然后再被回收。(引申一下,与此文无关 函数里可能会用到很多java对象。这也是为什么如果对象实现了finalize函数,不仅会使其生命周期至少延长一个GC过程,而且也会延长其所引用到的对象的生命周期,从而给内存造成了不必要的压力)

由于BitmapFinalizer实现了finalize()方法,当bitmap对象变成GC root不可达时,会触发回收BitmapFinalizer,放到延时回收队列中,调用它的finalize函数,进行bitmap native内存回收。

为什么bitmap对象不直接实现finalize()方法呢?因为bitmap2.3-7.0版本,主要内存(如像素点)在java堆中,如果直接实现finalize()方法会导致bitmap对象被延时回收,造成内存压力。

在 Android N 和 Android O 上做了些改动,没有了 BitmapFinalizer 类,但在 new Bitmap 时会注册 native 的 Finalizer 方法见: /frameworks/base/graphics/java/android/graphics/Bitmap.java

代码块

/**
     * Private constructor that must received an already allocated native bitmap
     * int (pointer).
     */
    // called from JNI
    Bitmap(long nativeBitmap, int width, int height, int density,
            boolean isMutable, boolean requestPremultiplied,
            byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
        if (nativeBitmap == 0) {
            throw new RuntimeException("internal error: native bitmap is 0");
        }
​
        mWidth = width;
        mHeight = height;
        mIsMutable = isMutable;
        mRequestPremultiplied = requestPremultiplied;
​
        mNinePatchChunk = ninePatchChunk;
        mNinePatchInsets = ninePatchInsets;
        if (density >= 0) {
            mDensity = density;
        }
​
        mNativePtr = nativeBitmap;
        long nativeSize = NATIVE_ALLOCATION_SIZE + getAllocationByteCount();
        NativeAllocationRegistry registry = new NativeAllocationRegistry(
                Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
        registry.registerNativeAllocation(this, nativeBitmap);
    }

NativeAllocationRegistry 见:
/libcore/luni/src/main/java/libcore/util/NativeAllocationRegistry.java

代码块

public class NativeAllocationRegistry {
​
        private final ClassLoader classLoader;
        private final long freeFunction;
        private final long size;
​
        /**
         * Constructs a NativeAllocationRegistry for a particular kind of native
         * allocation.
         * The address of a native function that can be used to free this kind
         * native allocation should be provided using the
         * <code>freeFunction</code> argument. The native function should have the
         * type:
         * <pre>
         *    void f(void* nativePtr);
         * </pre>
         * <p>
         * The <code>classLoader</code> argument should be the class loader used
         * to load the native library that freeFunction belongs to. This is needed
         * to ensure the native library doesn't get unloaded before freeFunction
         * is called.
         * <p>
         * The <code>size</code> should be an estimate of the total number of
         * native bytes this kind of native allocation takes up. Different
         * NativeAllocationRegistrys must be used to register native allocations
         * with different estimated sizes, even if they use the same
         * <code>freeFunction</code>.
         *
         * @param classLoader  ClassLoader that was used to load the native
         *                     library freeFunction belongs to.
         * @param freeFunction address of a native function used to free this
         *                     kind of native allocation
         * @param size         estimated size in bytes of this kind of native
         *                     allocation
         * @throws IllegalArgumentException If <code>size</code> is negative
         */
        public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
            if (size < 0) {
                throw new IllegalArgumentException("Invalid native allocation size: " + size);
            }
​
            this.classLoader = classLoader;
            this.freeFunction = freeFunction;
            this.size = size;
        }
​
        /**
         * Registers a new native allocation and associated Java object with the
         * runtime.
         * This NativeAllocationRegistry's <code>freeFunction</code> will
         * automatically be called with <code>nativePtr</code> as its sole
         * argument when <code>referent</code> becomes unreachable. If you
         * maintain copies of <code>nativePtr</code> outside
         * <code>referent</code>, you must not access these after
         * <code>referent</code> becomes unreachable, because they may be dangling
         * pointers.
         * <p>
         * The returned Runnable can be used to free the native allocation before
         * <code>referent</code> becomes unreachable. The runnable will have no
         * effect if the native allocation has already been freed by the runtime
         * or by using the runnable.
         *
         * @param referent  java object to associate the native allocation with
         * @param nativePtr address of the native allocation
         * @return runnable to explicitly free native allocation
         * @throws IllegalArgumentException if either referent or nativePtr is null.
         * @throws OutOfMemoryError         if there is not enough space on the Java heap
         *                                  in which to register the allocation. In this
         *                                  case, <code>freeFunction</code> will be
         *                                  called with <code>nativePtr</code> as its
         *                                  argument before the OutOfMemoryError is
         *                                  thrown.
         */
        public Runnable registerNativeAllocation(Object referent, long nativePtr) {
            if (referent == null) {
                throw new IllegalArgumentException("referent is null");
            }
            if (nativePtr == 0) {
                throw new IllegalArgumentException("nativePtr is null");
            }
​
            try {
                registerNativeAllocation(this.size);
            } catch (OutOfMemoryError oome) {
                applyFreeFunction(freeFunction, nativePtr);
                throw oome;
            }
​
            Cleaner cleaner = Cleaner.create(referent, new CleanerThunk(nativePtr));
            return new CleanerRunner(cleaner);
        }
​
​
        private class CleanerThunk implements Runnable {
            private long nativePtr;
​
            public CleanerThunk() {
                this.nativePtr = 0;
            }
​
            public CleanerThunk(long nativePtr) {
                this.nativePtr = nativePtr;
            }
​
            public void run() {
                if (nativePtr != 0) {
                    applyFreeFunction(freeFunction, nativePtr);
                }
                registerNativeFree(size);
            }
​
            public void setNativePtr(long nativePtr) {
                this.nativePtr = nativePtr;
            }
        }
​
        private static class CleanerRunner implements Runnable {
            private final Cleaner cleaner;
​
            public CleanerRunner(Cleaner cleaner) {
                this.cleaner = cleaner;
            }
​
            public void run() {
                cleaner.clean();
            }
        }
​
        /**
         * Calls <code>freeFunction</code>(<code>nativePtr</code>).
         * Provided as a convenience in the case where you wish to manually free a
         * native allocation using a <code>freeFunction</code> without using a
         * NativeAllocationRegistry.
         */
        public static native void applyFreeFunction(long freeFunction, long nativePtr);
    }

核心代码NativeAllocationRegistry里面的registerNativeAllocation方法:

代码块

try {
                registerNativeAllocation(this.size);
            } catch (OutOfMemoryError oome) {
                applyFreeFunction(freeFunction, nativePtr);
                throw oome;
            }
​
            Cleaner cleaner = Cleaner.create(referent, new CleanerThunk(nativePtr));
            return new CleanerRunner(cleaner);

我们具体分析一下各行的作用:

registerNativeAllocation(this.size);调用了

VMRuntime.getRuntime().registerNativeAllocation(int size)

http://androidxref.com/8.0.0_r4/xref/libcore/libart/src/main/java/dalvik/system/VMRuntime.java#316

看一下方法注释:

代码块

/**
309     * Registers a native allocation so that the heap knows about it and performs GC as required.
310     * If the number of native allocated bytes exceeds the native allocation watermark, the
311     * function requests a concurrent GC. If the native bytes allocated exceeds a second higher
312     * watermark, it is determined that the application is registering native allocations at an
313     * unusually high rate and a GC is performed inside of the function to prevent memory usage
314     * from excessively increasing.
315     */

通过匿名内存申请了mSize这么多native内存,向JVM坦白了偷内存的犯罪事实,仅仅是一个声明的作用,可能申请失败,抛出oom,此时进行释放;

接下来看一下

Cleaner cleaner = Cleaner.create(referent, new CleanerThunk(nativePtr));

通过sun.misc.Cleaner创建了一个对象。这个Cleaner来头可不小,它专门用于监控无法被JVM释放的内存。构造函数传入两个参数,一个是监控对象,这里是FileDescriptor对应的内存区域。Cleaner利用虚引用(PhantomReference)和ReferenceQueue来监控一个对象是否存在强引用。虚引用不影响对象任何的生命周期,当这个对象不具有强引用的时候,JVM会将这个对象加入与之关联的ReferenceQueue。此时Cleaner将会调用构造函数的第二个参数——一个Closer对象——实际上是一个Runnable来执行内存回收操作。

这里Runnable是CleanerThunk(nativePtr),用来释放native内存;

cleaner源码:http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/sun/misc/Cleaner.java

 

好了,到这里,我们就清楚了Bitmap在各个Android版本的回收机制;

具体回收细节属于原创,原创不易,转载请注明出处https://blog.csdn.net/shihongyu12345/article/details/89681948,谢谢!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/shihongyu12345/article/details/89681948

智能推荐

河北农业大学C语言题目,河北农业大学2010-2011学年现科C语言考试试题及答案.doc...-程序员宅基地

文章浏览阅读240次。河北农业大学2010-2011学年现科C语言考试试题及答案河北农业大学课程考试试卷2010—2011学年第2学期 2010 级 独立学院 专业 卷别:A考试科目: C语言程序设计 考核方式: 闭卷考试姓 名: 学号: 专业班级:(注:考生务必将答案写在答题纸上,写在本试卷上无效)本试卷共( 3 )页选择题(共30分,每题1..._河北农业大学c语言程序设计答案

2743:字符串判等_字符串判等 查看提交统计提问 总时间限制: 1000ms 内存限制: 65536kb 描述-程序员宅基地

文章浏览阅读1.2k次。2743:字符串判等查看提交统计提示提问总时间限制: 1000ms 内存限制: 65536kB描述字符串的相关比较 strcmp strlen 最后为了容易比较 在字符串后加上 '\0' 代表字符串的结束判断两个由大小写字母和空格组成的字符串在忽略大小写,且忽略空格后是否相等。输入两行,每行包含一个字符串。输出若两个字符串_字符串判等 查看提交统计提问 总时间限制: 1000ms 内存限制: 65536kb 描述

iOS11 打开相册上移问题解决方法_ios wkwebview打开相册 照片上移-程序员宅基地

文章浏览阅读1k次。原因是app设置了:if (@available(iOS 11, *)) { UIScrollView.appearance.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; }解决方案 :在弹出系统相册前添加 if (@available(iOS 11, *)) { UIScr..._ios wkwebview打开相册 照片上移

pyecharts 标准线_数据可视化之pyecharts-程序员宅基地

文章浏览阅读1.8k次。EChartsECharts是什么?下面是来自官方的介绍:ECharts,缩写来自Enterprise Charts,商业级数据图表,一个纯Javascript的图表库,可以流畅的运行在PC和移动设备上,兼容当前绝大部分浏览器(IE6/7/8/9/10/11,chrome,firefox,Safari等),底层依赖轻量级的Canvas类库ZRender,提供直观,生动,可交互,可高度个性化定制的数..._pyecharts添加鼠标绘制工具线

计算机网络应用层课后习题练习(二)_6-40用asn.1基本编码规则对以下4个数组(sequence-of)进行编码。假定每一个数字占-程序员宅基地

文章浏览阅读8.2k次,点赞8次,收藏30次。计算机网络应用层课后习题练习(一)计算机网络应用层课后习题练习(二)应用层知识点概览课后习题练习(二)应用层知识点概览课后习题练习(二)6-17在浏览 器中应当有几个可选解释程序。试给出一些可选解释程序的名称。HTML解释器,java解释器HTML解释器必备,有某些java小程序需要用到java解释器6-18 - 一个万维网网点有1000万个页面,平均每个页面有10个超链。读取- -..._6-40用asn.1基本编码规则对以下4个数组(sequence-of)进行编码。假定每一个数字占

Android通讯录查询篇--ContactsContract.Data开篇_kotlin 通讯录 查询-程序员宅基地

文章浏览阅读2.8k次。正在学习Android,想先找个简单点的东西练习一下,先想到的是通讯录,不过关于手机通讯录方面的资料在我现有的书和视频上都很少看到。没办法只有自己看SDK Docs了。  不管怎么说还是先赞Google的Android Docs,确实很全面,只要你想找就一定能找到你要的东西。不过我感觉想把里面的各个类的关系联系起来还是有点困难,特别是像英文水平过于一般的我,看的那叫个累呀。  好了,还是来说一下正题吧。Android刚开始在通讯录方面好像是提供的Contact_kotlin 通讯录 查询

随便推点

流瞬ElectroMagneticWorks(EMWorks).EMS.2017.SP1.4.for.SW2011-2018.Win64三维电磁场仿真软件 EMS帮助设计人员计算的电、磁、机械和-程序员宅基地

文章浏览阅读3.2k次。流瞬ElectroMagneticWorks(EMWorks).EMS.2017.SP1.4.for.SW2011-2018.Win64三维电磁场仿真软件 EMS帮助设计人员计算的电、磁、机械和热参数包括力、转矩、磁通密度、磁场、电场、电流、流、涡流、电感、电容、电阻、磁链、损耗、饱和,感应电压,力密度,功率损耗,温度、温度梯度、热通量和更。EMS是一种基于强大的有限元方法的三维电磁场仿真_emworks

Mat::isContinuous()接口_mat n_iscontinuous-程序员宅基地

文章浏览阅读537次。这里的continue的意思是在内存上continue,正常情况下,头一行的末尾在内存里和下一行的开头是相连的,但是有时候我们做了一些操作,选取了Mat 的一部分,例如选了一个ROI 这时候就不满足上面说的相连了。那么这时候continuous就被判定为假。..._mat n_iscontinuous

android 8.0系统原生锁屏流程分析_android timeout锁屏流程 源码-程序员宅基地

文章浏览阅读2.1k次。android 8.0,9.0系统锁屏流程分析_android timeout锁屏流程 源码

中标麒麟OS连接win10上的SMB共享_中标麒麟访问windows共享-程序员宅基地

文章浏览阅读1.2w次。使用中标麒麟文件共享Samba功能,主要用的是开始菜单里的连接到服务器,或者文件浏览器里的访问服务器功能!!!文件共享实施手册(仅供参考)中标麒麟系列OS访问windows上的共享文件夹(SMB)1.开启win上的Samba共享服务(控制面板-程序-启用或关闭windows功能,如图1)2.设置文件夹共享2.1(右击文件夹-属性-共享,添加访问的用户名,没有可以新建,新建的时候跳过邮箱登陆即可,如图2。选择Everyone要配置好相应的权限及设置图5相关选项)2.2点击高级共享选项,设置._中标麒麟访问windows共享

React Native与原生模块、组件之间的关系浅析(二)_reactnative 安卓constantstoexport-程序员宅基地

文章浏览阅读502次。那么书接上回,今天主要是继续探究React Native与原生模块的架构方式。原生模块原生模块可以访问activity、运行环境、GPS、存储空间等。原生模块就是能够在JavaScript层调用的API。因为对原生模块的全部请求都要异步执行。如果原生方法需要为JavaScript层的调用返回数据,该操作将通过promise或者回调函数来完成。React Native为这两种方式都提供了..._reactnative 安卓constantstoexport

不同类型的设备对font-weight的支持情况_安卓能识别的字体粗细font-weight: bold;-程序员宅基地

文章浏览阅读1.6k次,点赞2次,收藏4次。安卓系统:ios系统:可以看出:对于汉字 => 安卓和ios的区分是一样的,只有normal和bold。但是注意一点,ios的加粗是从600往后开始算的,安卓的是从700开始算的。所以我们日常要是给文字加粗,正常就是写font-weight:bold/700;对于字母和数字 => 安卓是有对于不同数值的粗细区分的,但是ios没有,跟汉字一样只有normal和bold。另外,lighter只针对安卓有效果,相当于是100/200的粗细效果。normal统一都是400,bold是700_安卓能识别的字体粗细font-weight: bold;

推荐文章

热门文章

相关标签