是否须要主动调用Bitmap的recycle方法

一个Bitmap使用完后,是只须要等它成为垃圾后让GC去回收,仍是应该主动调用recycle方法呢?或者说,主动调用recycle方法是否有好处,是否能立刻回收内存呢?java

 

带着这个问题来看源码(我看的4.4源码)。app

 

先看Bitmap内存的建立,经过跟踪Bitmap.createBitmap方法,能够发现是native方法里调用的JVM来建立的:spa

    jbyteArray arrayObj = env->NewByteArray(size);指针

native使用的是经过其获得的一个固定地址:对象

    jbyte* addr = jniGetNonMovableArrayElements(&env->functions, arrayObj);ip

native里会用一个SkPixelRef来存放他们:内存

    SkPixelRef* pr = new AndroidPixelRef(env, bitmapInfo, (void*) addr, bitmap->rowBytes(), arrayObj, ctable);资源

而后将这个传给Bitmap:rem

    bitmap->setPixelRef(pr);get

 

再看recycle过程:

java端:

    mBuffer = null;

native端:

    Caches::getInstance().textureCache.removeDeferred(bitmap);
    fPixelRef->unref();   // fPixelRef是上面分配的SkPixelRef
    fPixelRef = NULL;

这里其实就是java端将mBuffer置为垃圾。native端释放SkPixelRef,并延迟删除其对应的TextureCache(最终的删除应该是在下一帧开始前)。

 

再看Bitmap的finalize,发现Bitmap类本身没有finalize,专门用了一个静态内部类BitmapFinalizer,其finalize方法来作native资源的释放。至于为何要这么弄,我后面另说。

其会调用到native里:

    Caches::getInstance().textureCache.removeDeferred(resource);
    delete resource;

延迟删除其对应的TextureCache,并删除SkBitmap。

 

从这么来看,recycle方法会释放部分native内存,但并不会释放Bitmap占用内存最大的图像数据内存。
但我忽然想到,好像截屏时获得的Bitmap的图像数据内存并非在JVM里申请的,查看代码,果真是这样。其并非经过Bitmap.createBitmap方法建立的图像:

    GraphicsJNI::createBitmap(env, bitmap, GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL);

其使用的SkPixelRef也不是上面的AndroidPixelRef,而是ScreenshotPixelRef,里面持有着图像数据。

在这种状况下,调用recycle方法是会释放其图像数据的。

另外要命的是,假如咱们不停截屏并丢掉以前的Bitmap,咱们可能以为很容易就会有垃圾回收,那么以前的Bitmap就回收了。但是因为Bitmap的图像数据才是内存大户,Bitmap自己占用内存很是小,所以这种状况下Bitmap的构造引发垃圾回收的可能性很低。

我作了个试验,在app里点击按钮截一次屏,而后立刻扔掉,使其为垃圾。我不停点击按钮,最终系统内存耗尽致使app被杀,也没有发生GC。改成每次截屏后手动调用gc,就不会致使内存增大。

而且危险的是,这部份内存并非分配在app端,就算app被杀也不会释放。(截屏的内存是在SurfaceFlinger端申请的,app端的释放应该只是把内存使用权还给SurfaceFlinger,SurfaceFlinger会继续重用它,但不会完全释放还给系统,所以变大以后不会变小,不清楚有没有最终的释放逻辑。我之前在作系统截屏的时候曾由于没有主动recycle致使占用极大系统内存。)

 

画个表格来讲明一下recycle在各类状况下会回收哪些内存吧:

 

SkPixelRef

(小)

SkBitmap

(小)

图像数据

(面积大则很大)

TextureCache

(同图像数据至关)

JVM中分配图像数据如Bitmap.createBitmap

且没有被硬件加速draw过

× × 无此内存

JVM中分配图像数据如Bitmap.createBitmap

且有被硬件加速draw过

× ×

不在JVM中分配图像数据如截屏

× 状况同上

这些内存在其Bitmap成为垃圾后的垃圾回收过程里都会释放。可是,在native内存吃紧的状况下系统是不知道能够经过GC来回收一部分native内存的,因此尽早释放是有积极做用的。

 

结论:尽快的调用recycle是个好习惯,会释放与其相关的native分配的内存;但通常状况下其图像数据是在JVM里分配的,调用recycle并不会释放这部份内存。

咱们用createBitmap建立的Bitmap且没有被硬件加速Canvas draw过,则主动调用recycle产生的意义比较小,仅释放了native里的SkPixelRef的内存,这种状况我以为能够不主动调用recycle。

被硬件加速Canvas draw过的因为有TextureCache应该尽快调用recycle来尽早释放其TextureCache。

像截屏这种不是在JVM里分配内存的状况也应该尽快调用recycle来立刻释放其图像数据。

(一个例外,若是是经过Resources.getDrawable获得的Bitmap,不该该调用recycle,由于它可能会被重用)

 

另,说一下上面提到的Bitmap的finalize实现方式。
这里没有直接在Bitmap上实现finalize,而是用一个静态内部类专门实现finalize,这是由于在GC过程当中,没有finalize的对象能够直接回收,而有finalize的对象须要多保持一下子来执行其finalize方法,而后才能回收,在我之前了解的C#的垃圾回收机制里,有finalize的对象在第一次GC的时候只会执行其finalize方法,要到下一次GC才会回收其内存。因此Bitmap的这种finalize实现方式是为了让占用内存大的部分(Bitmap类)没有finalize,能够早点释放;BitmapFinalizer内部类仅持有一个NativeBitmap指针,经过finalize去释放native内存。这样最有效的达到既提早释放主要内存又能经过finalize释放native内存的目的。

相关文章
相关标签/搜索