是否需要主動調用Bitmap的recycle方法


一個Bitmap使用完后,是只需要等它成為垃圾后讓GC去回收,還是應該主動調用recycle方法呢?或者說,主動調用recycle方法是否有好處,是否能馬上回收內存呢?

 

帶着這個問題來看源碼(我看的4.4源碼)。

 

先看Bitmap內存的創建,通過跟蹤Bitmap.createBitmap方法,可以發現是native方法里調用的JVM來創建的:

    jbyteArray arrayObj = env->NewByteArray(size);

native使用的是通過其得到的一個固定地址:

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

native里會用一個SkPixelRef來存放他們:

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

然后將這個傳給Bitmap:

    bitmap->setPixelRef(pr);

 

再看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內存的目的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM