包括 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());
}
}
在 8.0 中薛闪,手動調用 recycle() 方法,像素數(shù)據(jù)會立即釋放俺陋;
在 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 的原理與設計思想