主要参照了这篇博客,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 dobreak;case PixelStorageType::External:mPixelStorage.external.freeFunc(mPixelStorage.external.address,183mPixelStorage.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);}}
​@Overridepublic 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 JNIBitmap(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,谢谢!

最详细的Android Bitmap回收机制(从2.3到7.0,8.0)相关推荐

  1. android 内存回收机制

    Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化, 使得其进程调度与资源管 ...

  2. 详细介绍Java垃圾回收机制

    垃圾收集GC(Garbage Collection)是Java语言的核心技术之一,之前我们曾专门探讨过Java 7新增的垃圾回收器G1的新特性,但在JVM的内部运行机制上看,Java的垃圾回收原理与机 ...

  3. android bitmap回收,android BitMap回收

    bitmap在android中使用较多,但是如果不对其进行回收,将会导致内存问题. [第一种方法]及时回收bitmap内存: 一般而言,回收bitmap内存可以用到以下代码 if(bitmap != ...

  4. android垃圾回收机制

    垃圾内存不及时回收,则运行时的可用内存会越来越少,最终导致OOM(内存溢出).而垃圾回收(GC),主要可从两个方面探讨: 1.怎么判定是"垃圾"? 2.怎么回收的? 1." ...

  5. android BitMap回收

    第一种方法--及时回收bitmap内存: 一般而言,回收bitmap内存可以用到以下代码 if(bitmap != null && !bitmap.isRecycled()){  bi ...

  6. linux内存回收(二)--直接内存回收机制

    上一章,我们学习了kswapd的内存回收的机制,其本身是一个内核线程,它和调用者的关系是异步的,那么本章就开始学习内核的内存回收的方式.因为在不同的内存分配路径中,会触发不同的内存回收方式,内存回收针 ...

  7. Java垃圾回收机制(Garbage Collection)

    引用博客地址:http://www.cnblogs.com/ywl925/p/3925637.html 以下两篇博客综合描述Java垃圾回收机制 第一篇:说的比较多,但是不详细 http://www. ...

  8. Android 事件分发机制

    Android 事件分发机制  demo验证:  https://blog.csdn.net/hty1053240123/article/details/77866302 目录 1.基础认知 2.事件 ...

  9. Android 系统(199)---Android事件分发机制详解

    Android事件分发机制详解 前言 Android事件分发机制是Android开发者必须了解的基础 网上有大量关于Android事件分发机制的文章,但存在一些问题:内容不全.思路不清晰.无源码分析. ...

最新文章

  1. 走在浪潮尖端——为什么选择云计算
  2. jsp mysql数据修改不了了_通过JSP界面无法修改mysql中的数据
  3. 产品经理必须知道的一些知识:决定价格的四种因素
  4. 百度2015校园招聘软件开发笔试题及答案
  5. java tomcat 日志_java – 访问Tomcat中的详细日志
  6. 在DataGridView中显示合计,并且合计始终在最后一行
  7. uva 550 有趣的乘法(dfs)
  8. AfxMessageBox
  9. 遇到的问题:uboot下,关闭串口前需要printf打印一个“UART BUS OFF!!!”提示信息,但是打印不出来
  10. 究竟先操作缓存,还是数据库?
  11. 数据库技术与应用课程设计-学生信息管理系统
  12. 计算机科学与技术专业课程简介
  13. Docker安装PHP-FPM5.6 (自带redis扩展,Mysql扩展,GD库扩展(支持JEPG))
  14. Asp.net Ajax框架教程[教程下载]
  15. .NET组件与控件开发
  16. 图片题注和章节不对应
  17. 点云库PCL学习笔记 -- 点云滤波Filtering -- 3. StatisticalOutlierRemoval 统计滤波器
  18. 什么是外键约束?外键约束下的多表操作是什么?
  19. SpringBoot OA办公系统
  20. 论相对性原理2-洛仑兹坐标变换的数学原理

热门文章

  1. Latex中subfloat设置子图标题字体大小
  2. 数据挖掘知识点整理(期末复习版)
  3. Redis 面试面面观
  4. 阿里云 实人认证(详细)RPMin
  5. dorehtml.php,脚本命令 window.open 的所有参数详解及实例 涉及页面执行后跳转和多久跳转...
  6. 微信开放平台 安卓Android 应用签名生成
  7. 小米 MIX4 发布、三年要拿下全球第一、还有一只 9999 元的狗?快看 3 个小时雷军都讲了什么!...
  8. 恒源云(GPUSHARE)_未闻Prompt名(论文学习笔记)
  9. 前端如何实现搜索记录展示以及清楚历史搜索记录
  10. Poser v7.0 1DVD(3D 角色动画)