在Android O上大面积的爆了大量native Bitmap相关的泄漏问题,最大能达到几十MB,开始怀疑是出现了native内存泄漏问题,但经分析后发现是Android N和Android O在处理Bitmap的内存存储不同导致的问题,并不是内存泄漏~
以下是整体分析记录:
内存申请
由于Bitmap构造方法不是public的,使用Bitmap时,一般都是使用BitmapFactory的decode操作,以BitmapFactory.decodeFile接口来看下Bitmap申请内存的差异性,看执行流的差异
Android O
BitmapFactory.decodeFile->BitmapFactory.decodeStream->BitmapFactory.decodeStreamInternal->BitmapFactory.nativeDecodeStream->BitmapFactory.cpp nativeDecodeStream->BitmapFactory.cpp doDecode
在doDecode方法中有两段与申请内存相关,一个是默认的内存分配器为HeapAllocator,一个是调用方法tryAllocPixels继续申请内存,见xref: /frameworks/base/core/jni/android/graphics/BitmapFactory.cpp
384 HeapAllocator defaultAllocator;
385 RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize);
386 ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
387 SkBitmap::HeapAllocator heapAllocator;
388 SkBitmap::Allocator* decodeAllocator;
389 if (javaBitmap != nullptr && willScale) {
390 // This will allocate pixels using a HeapAllocator, since there will be an extra
391 // scaling step that copies these pixels into Java memory. This allocator
392 // also checks that the recycled javaBitmap is large enough.
393 decodeAllocator = &scaleCheckingAllocator;
394 } else if (javaBitmap != nullptr) {
395 decodeAllocator = &recyclingAllocator;
396 } else if (willScale || isHardware) {
397 // This will allocate pixels using a HeapAllocator,
398 // for scale case: there will be an extra scaling step.
399 // for hardware case: there will be extra swizzling & upload to gralloc step.
400 decodeAllocator = &heapAllocator;
401 } else {
402 decodeAllocator = &defaultAllocator;
403 }
404
405 // Construct a color table for the decode if necessary
406 sk_sp<SkColorTable> colorTable(nullptr);
407 SkPMColor* colorPtr = nullptr;
408 int* colorCount = nullptr;
409 int maxColors = 256;
410 SkPMColor colors[256];
411 if (kIndex_8_SkColorType == decodeColorType) {
412 colorTable.reset(new SkColorTable(colors, maxColors));
413
414 // SkColorTable expects us to initialize all of the colors before creating an
415 // SkColorTable. However, we are using SkBitmap with an Allocator to allocate
416 // memory for the decode, so we need to create the SkColorTable before decoding.
417 // It is safe for SkAndroidCodec to modify the colors because this SkBitmap is
418 // not being used elsewhere.
419 colorPtr = const_cast<SkPMColor*>(colorTable->readColors());
420 colorCount = &maxColors;
421 }
422
423 SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied);
424
425 const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(),
426 decodeColorType, alphaType, decodeColorSpace);
427
428 // For wide gamut images, we will leave the color space on the SkBitmap. Otherwise,
429 // use the default.
430 SkImageInfo bitmapInfo = decodeInfo;
431 if (decodeInfo.colorSpace() && decodeInfo.colorSpace()->isSRGB()) {
432 bitmapInfo = bitmapInfo.makeColorSpace(GraphicsJNI::colorSpaceForType(decodeColorType));
433 }
434
435 if (decodeColorType == kGray_8_SkColorType) {
436 // The legacy implementation of BitmapFactory used kAlpha8 for
437 // grayscale images (before kGray8 existed). While the codec
438 // recognizes kGray8, we need to decode into a kAlpha8 bitmap
439 // in order to avoid a behavior change.
440 bitmapInfo =
441 bitmapInfo.makeColorType(kAlpha_8_SkColorType).makeAlphaType(kPremul_SkAlphaType);
442 }
443 SkBitmap decodingBitmap;
444 if (!decodingBitmap.setInfo(bitmapInfo) ||
445 !decodingBitmap.tryAllocPixels(decodeAllocator, colorTable.get())) {
446 // SkAndroidCodec should recommend a valid SkImageInfo, so setInfo()
447 // should only only fail if the calculated value for rowBytes is too
448 // large.
449 // tryAllocPixels() can fail due to OOM on the Java heap, OOM on the
450 // native heap, or the recycled javaBitmap being too small to reuse.
451 return nullptr;
452 }
tryAllocPixels方法会调用默认内存分配器的allocPixelRef方法,见xref: /external/skia/src/core/SkBitmap.cpp
313 bool SkBitmap::tryAllocPixels(Allocator* allocator, SkColorTable* ctable) {
314 HeapAllocator stdalloc;
315
316 if (nullptr == allocator) {
317 allocator = &stdalloc;
318 }
319 return allocator->allocPixelRef(this, ctable);
320}
allocPixelRef方法会继续调用allocateHeapBitmap分配内存,见xref: /frameworks/base/core/jni/android/graphics/Graphics.cpp
616 bool HeapAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
617 mStorage = android::Bitmap::allocateHeapBitmap(bitmap, ctable);
618 return !!mStorage;
619}
allocateHeapBitmap方法会最终new Bitmap,分配内存 ,见xref: /frameworks/base/libs/hwui/hwui/Bitmap.cpp
86 static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes,
87 SkColorTable* ctable) {
88 void* addr = calloc(size, 1);
89 if (!addr) {
90 return nullptr;
91 }
92 return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes, ctable));
93}
Android N
开始的执行流不变:
BitmapFactory.decodeFile->BitmapFactory.decodeStream->BitmapFactory.decodeStreamInternal->BitmapFactory.nativeDecodeStream->BitmapFactory.cpp nativeDecodeStream->BitmapFactory.cpp doDecode
差别主要是体现在BitmapFactory的代码变化,见:xref: /frameworks/base/core/jni/android/graphics/BitmapFactory.cpp
343 JavaPixelAllocator javaAllocator(env);
344 RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize);
345 ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
346 SkBitmap::HeapAllocator heapAllocator;
347 SkBitmap::Allocator* decodeAllocator;
348 if (javaBitmap != nullptr && willScale) {
349 // This will allocate pixels using a HeapAllocator, since there will be an extra
350 // scaling step that copies these pixels into Java memory. This allocator
351 // also checks that the recycled javaBitmap is large enough.
352 decodeAllocator = &scaleCheckingAllocator;
353 } else if (javaBitmap != nullptr) {
354 decodeAllocator = &recyclingAllocator;
355 } else if (willScale) {
356 // This will allocate pixels using a HeapAllocator, since there will be an extra
357 // scaling step that copies these pixels into Java memory.
358 decodeAllocator = &heapAllocator;
359 } else {
360 decodeAllocator = &javaAllocator;
361 }
362
363 // Set the decode colorType. This is necessary because we can't always support
364 // the requested colorType.
365 SkColorType decodeColorType = codec->computeOutputColorType(prefColorType);
366
367 // Construct a color table for the decode if necessary
368 SkAutoTUnref<SkColorTable> colorTable(nullptr);
369 SkPMColor* colorPtr = nullptr;
370 int* colorCount = nullptr;
371 int maxColors = 256;
372 SkPMColor colors[256];
373 if (kIndex_8_SkColorType == decodeColorType) {
374 colorTable.reset(new SkColorTable(colors, maxColors));
375
376 // SkColorTable expects us to initialize all of the colors before creating an
377 // SkColorTable. However, we are using SkBitmap with an Allocator to allocate
378 // memory for the decode, so we need to create the SkColorTable before decoding.
379 // It is safe for SkAndroidCodec to modify the colors because this SkBitmap is
380 // not being used elsewhere.
381 colorPtr = const_cast<SkPMColor*>(colorTable->readColors());
382 colorCount = &maxColors;
383 }
384
385 // Set the alpha type for the decode.
386 SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied);
387
388 const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType,
389 alphaType);
390 SkImageInfo bitmapInfo = decodeInfo;
391 if (decodeColorType == kGray_8_SkColorType) {
392 // The legacy implementation of BitmapFactory used kAlpha8 for
393 // grayscale images (before kGray8 existed). While the codec
394 // recognizes kGray8, we need to decode into a kAlpha8 bitmap
395 // in order to avoid a behavior change.
396 bitmapInfo = SkImageInfo::MakeA8(size.width(), size.height());
397 }
398 SkBitmap decodingBitmap;
399 if (!decodingBitmap.setInfo(bitmapInfo) ||
400 !decodingBitmap.tryAllocPixels(decodeAllocator, colorTable)) {
401 // SkAndroidCodec should recommend a valid SkImageInfo, so setInfo()
402 // should only only fail if the calculated value for rowBytes is too
403 // large.
404 // tryAllocPixels() can fail due to OOM on the Java heap, OOM on the
405 // native heap, or the recycled javaBitmap being too small to reuse.
406 return nullptr;
407 }
408
tryAllocPixels方法依然会调用默认内存分配器的allocPixelRef方法,不过不再是HeapAllicator,而是JavaPixelAllocator,官方解释: Allocator which allocates the backing buffer in the Java heap.
意思就是在Java Heap上分配内存,见xref: /external/skia/src/core/SkBitmap.cpp
278bool SkBitmap::tryAllocPixels(Allocator* allocator, SkColorTable* ctable) {
279 HeapAllocator stdalloc;
280
281 if (nullptr == allocator) {
282 allocator = &stdalloc;
283 }
284 return allocator->allocPixelRef(this, ctable);
285}
现在都已经进入native世界了,那又是怎么在Java Heap上分配内存的呢?有过JNI编程经验的同学可能不难想到,JNIEnv就是干这个的~接着往下看代码,见xref: /frameworks/base/core/jni/android/graphics/Graphics.cpp
672 bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
673 JNIEnv* env = vm2env(mJavaVM);
674
675 mStorage = GraphicsJNI::allocateJavaPixelRef(env, bitmap, ctable);
676 return mStorage != nullptr;
677}
继续看GraphicsJNI::allocateJavaPixelRef方法,见xref: /frameworks/base/core/jni/android/graphics/Graphics.cpp
486android::Bitmap* GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
487 SkColorTable* ctable) {
488 const SkImageInfo& info = bitmap->info();
489 if (info.colorType() == kUnknown_SkColorType) {
490 doThrowIAE(env, "unknown bitmap configuration");
491 return NULL;
492 }
493
494 size_t size;
495 if (!computeAllocationSize(*bitmap, &size)) {
496 return NULL;
497 }
498
499 // we must respect the rowBytes value already set on the bitmap instead of
500 // attempting to compute our own.
501 const size_t rowBytes = bitmap->rowBytes();
502
503 jbyteArray arrayObj = (jbyteArray) env->CallObjectMethod(gVMRuntime,
504 gVMRuntime_newNonMovableArray,
505 gByte_class, size);
506 if (env->ExceptionCheck() != 0) {
507 return NULL;
508 }
509 SkASSERT(arrayObj);
510 jbyte* addr = (jbyte*) env->CallLongMethod(gVMRuntime, gVMRuntime_addressOf, arrayObj);
511 if (env->ExceptionCheck() != 0) {
512 return NULL;
513 }
514 SkASSERT(addr);
515 android::Bitmap* wrapper = new android::Bitmap(env, arrayObj, (void*) addr,
516 info, rowBytes, ctable);
517 wrapper->getSkBitmap(bitmap);
518 // since we're already allocated, we lockPixels right away
519 // HeapAllocator behaves this way too
520 bitmap->lockPixels();
521
522 return wrapper;
523}
从上面就可以看到,new android::Bitmap,见xref: /frameworks/base/core/jni/android/graphics/Bitmap.cpp
129Bitmap::Bitmap(JNIEnv* env, jbyteArray storageObj, void* address,
130 const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable)
131 : mPixelStorageType(PixelStorageType::Java) {
132 env->GetJavaVM(&mPixelStorage.java.jvm);
133 mPixelStorage.java.jweakRef = env->NewWeakGlobalRef(storageObj);
134 mPixelStorage.java.jstrongRef = nullptr;
135 mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable));
136 // Note: this will trigger a call to onStrongRefDestroyed(), but
137 // we want the pixel ref to have a ref count of 0 at this point
138 mPixelRef->unref();
139}
从哪个地址上new呢?参数中有addr,这个addr是通过env调用gVMRuntime_addressOf获取的arrayObj的地址,而arrayObj是通过env调用gVMRuntime_newNonMovableArray创建的array,通过JNI方法进入Java世界创建的对象必然在Java Heap上~就像Zygote进入Java世界是通过JNI调用com.android.internal.os.ZygoteInit类的main函数是一个道理~
具体看下gVMRuntime_newNonMovableArray的实现吧,看看直接现场,见xref: /frameworks/base/core/jni/android/graphics/Graphics.cpp
855 gVMRuntime_class = make_globalref(env, "dalvik/system/VMRuntime");
856 m = env->GetStaticMethodID(gVMRuntime_class, "getRuntime", "()Ldalvik/system/VMRuntime;");
857 gVMRuntime = env->NewGlobalRef(env->CallStaticObjectMethod(gVMRuntime_class, m));
858 gVMRuntime_newNonMovableArray = env->GetMethodID(gVMRuntime_class, "newNonMovableArray",
859 "(Ljava/lang/Class;I)Ljava/lang/Object;");
860 gVMRuntime_addressOf = env->GetMethodID(gVMRuntime_class, "addressOf", "(Ljava/lang/Object;)J");
gVMRuntime_newNonMovableArray是dalvik/system/VMRuntime的newNonMovableArray方法,而newNonMovableArray是个native方法,从注释可以看出是在Java heap上分配内存,看下具体是怎么分配的吧,见
xref: /libcore/libart/src/main/java/dalvik/system/VMRuntime.java
253 /**
254 * Returns an array allocated in an area of the Java heap where it will never be moved.
255 * This is used to implement native allocations on the Java heap, such as DirectByteBuffers
256 * and Bitmaps.
257 */
258 public native Object newNonMovableArray(Class<?> componentType, int length);
后面会调到VMRuntime_newNonMovableArray,见xref: /art/runtime/native/dalvik_system_VMRuntime.cc
69 static jobject VMRuntime_newNonMovableArray(JNIEnv* env, jobject, jclass javaElementClass,
70 jint length) {
71 ScopedFastNativeObjectAccess soa(env);
72 if (UNLIKELY(length < 0)) {
73 ThrowNegativeArraySizeException(length);
74 return nullptr;
75 }
76 mirror::Class* element_class = soa.Decode<mirror::Class*>(javaElementClass);
77 if (UNLIKELY(element_class == nullptr)) {
78 ThrowNullPointerException("element class == null");
79 return nullptr;
80 }
81 Runtime* runtime = Runtime::Current();
82 mirror::Class* array_class =
83 runtime->GetClassLinker()->FindArrayClass(soa.Self(), &element_class);
84 if (UNLIKELY(array_class == nullptr)) {
85 return nullptr;
86 }
87 gc::AllocatorType allocator = runtime->GetHeap()->GetCurrentNonMovingAllocator();
88 mirror::Array* result = mirror::Array::Alloc<true>(soa.Self(), array_class, length,
89 array_class->GetComponentSizeShift(),
90 allocator);
91 return soa.AddLocalReference<jobject>(result);
92}
其意思就是runtime->GetHeap()上分配内存~及Java Heap
总结
Android N 上Bitmap的像素点数据与bitmap对象都是分配到dalvik heap,而Android O 上Bitmap的像素点数据是分配在native heap中,因此在Android O加载大量的Bitmap并不会导致应用OOM(我拿一个4GB的手机,native memory会增长搭到将近3GB,还是很恐怖的),但并不意味着就可以任意的使用native内存。所以在需要加载大量Bitmap的时候,该优化还是要优化,该缓存还是要缓存。只是对于某些将Bitmap通过JNI方式直接在native请求空间的优化方案来说,就失去意义了(之前阿里分享过一个偷native内存给Bitmap 的方案就失去意义了)。
内存回收
首先纠正一个观点,Bitmap.java的recycle方法并不是一定要调用的,这是很老的观点了~具体原因如下:
在Android 2.3.3之前,Bitmap像素数据和Bitmap对象是分开存储的,像素数据是存储在native memory中,对象存储在Dalvik heap中,native memory中的像素数据不是以一种可预见的方式释放,可能导致应用程序暂时超过其内存限制和崩溃,所以是需要通过recycle方法回收内存的,那个时候管理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.
private int mCacheRefCount = 0;
private int mDisplayRefCount = 0;
...
// Notify the drawable that the displayed state has changed.
// Keep a count to determine when the drawable is no longer displayed.
public void setIsDisplayed(boolean isDisplayed) {
synchronized (this) {
if (isDisplayed) {
mDisplayRefCount++;
mHasBeenDisplayed = true;
} else {
mDisplayRefCount--;
}
}
// Check to see if recycle() can be called.
checkState();
}
// Notify the drawable that the cache state has changed.
// Keep a count to determine when the drawable is no longer being cached.
public void setIsCached(boolean isCached) {
synchronized (this) {
if (isCached) {
mCacheRefCount++;
} else {
mCacheRefCount--;
}
}
// Check to see if recycle() can be called.
checkState();
}
private synchronized void checkState() {
// If the drawable cache and display ref counts = 0, and this drawable
// has been displayed, then recycle.
if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
&& hasValidBitmap()) {
getBitmap().recycle();
}
}
private synchronized boolean hasValidBitmap() {
Bitmap bitmap = getBitmap();
return bitmap != null && !bitmap.isRecycled();
}
在Android 3.0以后,Bitmap的像素数据和Bitmap对象一起存储在Dalvik heap中,因此不需要手动调用recycle释放Bitmap对象,内存的释放交给垃圾回收器来做~
但我看了下,在Android KK,L,M,N,O版本上,都能看到recycle接口,为什么没有干掉呢?
从上面的分析能知道,不管是在N上还是O上,都需要JNI方法创建Bitmap,简单来说,Bitmap加载至内存中以后,其实是有两部分内存,一个是Java部分,一个是Native部分,Java内存有虚拟机垃圾回收控制就好了,但C部分的内存还是需要recycle方法来释放~recycle接口的注释也说明了这一点~
339 /**
340 * Free the native object associated with this bitmap, and clear the
341 * reference to the pixel data. This will not free the pixel data synchronously;
342 * it simply allows it to be garbage collected if there are no other references.
343 * The bitmap is marked as "dead", meaning it will throw an exception if
344 * getPixels() or setPixels() is called, and will draw nothing. This operation
345 * cannot be reversed, so it should only be called if you are sure there are no
346 * further uses for the bitmap. This is an advanced call, and normally need
347 * not be called, since the normal GC process will free up this memory when
348 * there are no more references to this bitmap.
349 */
350 public void recycle() {
351 if (!mRecycled && mNativePtr != 0) {
352 if (nativeRecycle(mNativePtr)) {
353 // return value indicates whether native pixel object was actually recycled.
354 // false indicates that it is still in use at the native level and these
355 // objects should not be collected now. They will be collected later when the
356 // Bitmap itself is collected.
357 mBuffer = null;
358 mNinePatchChunk = null;
359 }
360 mRecycled = true;
361 }
362 }
注释的意思就是:释放与此位图相关的native对象,清除像素数据的引用~这将不会同步释放像素数据,只是允许在没有其他引用的情况下能被垃圾回收.位图被标记为“死”,意味着它将抛出一个异常getPixels()或setPixels被调用,什么也不画。此操作不能逆向,所以只有确定位图没有其他用途时方可调用它.这是一个高级调用,通常不需要调用,因为正常的GC进程将在没有对此位图的引用时释放该内存~什么时候没有此位图的引用呢,很容易啊想到Activity onDestroy的时候~后面会根据执行的log,验证这一点
那么如果不手动调用recycle方法,Bitmap 的内存又是如何回收的呢?在new Bitmap时,其实就已经指定了谁来控制Bitmap 的内存回收~Android M版本及以前的版本,Bitmap的内存回收主要是通过BitmapFinalizer来完成的,见xref: /frameworks/base/graphics/java/android/graphics/Bitmap.java
112 /**
113 * Private constructor that must received an already allocated native bitmap
114 * int (pointer).
115 */
116 // called from JNI
117 Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
118 boolean isMutable, boolean requestPremultiplied,
119 byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
120 if (nativeBitmap == 0) {
121 throw new RuntimeException("internal error: native bitmap is 0");
122 }
123
124 mWidth = width;
125 mHeight = height;
126 mIsMutable = isMutable;
127 mRequestPremultiplied = requestPremultiplied;
128 mBuffer = buffer;
129
130 mNinePatchChunk = ninePatchChunk;
131 mNinePatchInsets = ninePatchInsets;
132 if (density >= 0) {
133 mDensity = density;
134 }
135
136 mNativePtr = nativeBitmap;
137 mFinalizer = new BitmapFinalizer(nativeBitmap);
138 int nativeAllocationByteCount = (buffer == null ? getByteCount() : 0);
139 mFinalizer.setNativeAllocationByteCount(nativeAllocationByteCount);
140
141 // MIUI ADD:
142 miui.util.DumpBitmapInfoUtils.putBitmap(this, null);
143 }
简单看下BitmapFinalizer,见xref: /frameworks/base/graphics/java/android/graphics/Bitmap.java
1650 private static class BitmapFinalizer {
1651 private long mNativeBitmap;
1652
1653 // Native memory allocated for the duration of the Bitmap,
1654 // if pixel data allocated into native memory, instead of java byte[]
1655 private int mNativeAllocationByteCount;
1656
1657 BitmapFinalizer(long nativeBitmap) {
1658 mNativeBitmap = nativeBitmap;
1659 }
1660
1661 public void setNativeAllocationByteCount(int nativeByteCount) {
1662 if (mNativeAllocationByteCount != 0) {
1663 VMRuntime.getRuntime().registerNativeFree(mNativeAllocationByteCount);
1664 }
1665 mNativeAllocationByteCount = nativeByteCount;
1666 if (mNativeAllocationByteCount != 0) {
1667 VMRuntime.getRuntime().registerNativeAllocation(mNativeAllocationByteCount);
1668 }
1669 }
Bitmap自身没有Finalize方法,但为它包装了BitmapFinalizer类来完成内存回收,为什么要这样做?这是因为有Finalize方法的对象在第一次GC来临时并不会立即回收内存,需等到下一次GC才会真正回收内存(系统的许多工具类实现了object的finalize方法,这些类在创建时会新建FinalizerRefernece,封装override finalize方法的对象,是个强引用关系,GC时会将FinalizerReference放入静态链表ReferenceQueue(双向链表),FinalizerDaemon线程维护此链表,当线程获得执行资源时,从队列中弹出里面的FinalizerReference对象,并执行封装的object的finalize方法,而FinalizerWatchdogDeamon线程会监控 FinalizerDaemon线程的执行object.finalize()方法的快慢问题,如果ReferenceQueue是空的,FinalizerWatchdogDeamon线程会睡去,直到ReferenceQueue不为空,FinalizeDaemon线程会去唤醒FinalizerWatchdogDeamon,告诉它有工作来了,这是大概流程).所以Bitmap的finalize实现是为了让占用内存大的部分,可以早些释放内存,BitmapFinalizer内部类仅持有一个NativeBitmap指针,通过finalize去释放native内存。这样最有效的达到既提前释放主要内存又能通过finalize释放native内存的目的。
在Android N和Android O上做了些改动,但改动不大~虽然没有了BitmapFinalizer类,但在new Bitmap时会注册native的Finalizer方法,见xref: /frameworks/base/graphics/java/android/graphics/Bitmap.java
115 /**
116 * Private constructor that must received an already allocated native bitmap
117 * int (pointer).
118 */
119 // called from JNI
120 Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
121 boolean isMutable, boolean requestPremultiplied,
122 byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
123 if (nativeBitmap == 0) {
124 throw new RuntimeException("internal error: native bitmap is 0");
125 }
126
127 mWidth = width;
128 mHeight = height;
129 mIsMutable = isMutable;
130 mRequestPremultiplied = requestPremultiplied;
131 mBuffer = buffer;
132
133 mNinePatchChunk = ninePatchChunk;
134 mNinePatchInsets = ninePatchInsets;
135 if (density >= 0) {
136 mDensity = density;
137 }
138
139 mNativePtr = nativeBitmap;
140 // MIUI ADD:
141 miui.util.DumpBitmapInfoUtils.putBitmap(this, null);
142 long nativeSize = NATIVE_ALLOCATION_SIZE;
143 if (buffer == null) {
144 nativeSize += getByteCount();
145 }
146 NativeAllocationRegistry registry = new NativeAllocationRegistry(
147 Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
148 registry.registerNativeAllocation(this, nativeBitmap);
149 }
150
看下NativeAllocationRegistry的构造方法以及registerNativeAllocation方法,见xref: /libcore/luni/src/main/java/libcore/util/NativeAllocationRegistry.java
76 public NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size) {
77 if (size < 0) {
78 throw new IllegalArgumentException("Invalid native allocation size: " + size);
79 }
80
81 this.classLoader = classLoader;
82 this.freeFunction = freeFunction;
83 this.size = size;
84 }
86 /**
87 * Registers a new native allocation and associated Java object with the
88 * runtime.
89 * This NativeAllocationRegistry's <code>freeFunction</code> will
90 * automatically be called with <code>nativePtr</code> as its sole
91 * argument when <code>referent</code> becomes unreachable. If you
92 * maintain copies of <code>nativePtr</code> outside
93 * <code>referent</code>, you must not access these after
94 * <code>referent</code> becomes unreachable, because they may be dangling
95 * pointers.
96 * <p>
97 * The returned Runnable can be used to free the native allocation before
98 * <code>referent</code> becomes unreachable. The runnable will have no
99 * effect if the native allocation has already been freed by the runtime
100 * or by using the runnable.
101 *
113 public Runnable registerNativeAllocation(Object referent, long nativePtr) {
114 if (referent == null) {
115 throw new IllegalArgumentException("referent is null");
116 }
117 if (nativePtr == 0) {
118 throw new IllegalArgumentException("nativePtr is null");
119 }
120
121 try {
122 registerNativeAllocation(this.size);
123 } catch (OutOfMemoryError oome) {
124 applyFreeFunction(freeFunction, nativePtr);
125 throw oome;
126 }
127
128 Cleaner cleaner = Cleaner.create(referent, new CleanerThunk(nativePtr));
129 return new CleanerRunner(cleaner);
130 }
意思就是注册一个native内存回收器,此回收器和运行时的Java Bitmap对象相关联,当Java Bitmap对象不可达时内存回收方法(freeFunction)会自动被调用,其实就是呼应了上述在recycle方法中所说的
since the normal GC process will free up this memory when there are no more references to this bitmap.
在这个地方再插几句:Java层的Bitmap的构造方法中有如下标注:
// called from JNI
那有人会有疑问,Bitmap的构造方法又是什么时候执行的呢?
在Android N上createBitmap时会创建,见xref: /frameworks/base/core/jni/android/graphics/Graphics.cpp
409jobject GraphicsJNI::createBitmap(JNIEnv* env, android::Bitmap* bitmap,
410 int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
411 int density) {
412 bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
413 bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
414 // The caller needs to have already set the alpha type properly, so the
415 // native SkBitmap stays in sync with the Java Bitmap.
416 assert_premultiplied(bitmap->info(), isPremultiplied);
417
418 jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
419 reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(),
420 bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
421 ninePatchChunk, ninePatchInsets);
422 hasException(env); // For the side effect of logging.
423 return obj;
424}
即在BitmapFactory doDecode时得到执行,见xref: /frameworks/base/core/jni/android/graphics/BitmapFactory.cpp
538 // now create the java bitmap
539 return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(),
540 bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
541}
在Android O上稍微有点差别,但其基本一致~见xref: /frameworks/base/core/jni/android/graphics/Bitmap.cpp
199jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
200 int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
201 int density) {
202 bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
203 bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
204 // The caller needs to have already set the alpha type properly, so the
205 // native SkBitmap stays in sync with the Java Bitmap.
206 assert_premultiplied(bitmap->info(), isPremultiplied);
207 BitmapWrapper* bitmapWrapper = new BitmapWrapper(bitmap);
208 jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
209 reinterpret_cast<jlong>(bitmapWrapper), bitmap->width(), bitmap->height(), density,
210 isMutable, isPremultiplied, ninePatchChunk, ninePatchInsets);
211
212 if (env->ExceptionCheck() != 0) {
213 ALOGE("*** Uncaught exception returned from Java call!\n");
214 env->ExceptionDescribe();
215 }
216 return obj;
217}
根据不同的Bitmap 类型在不同的地方调用~
总结
从上面的分析可以得知,Android N上Bitmap的内存主要在Java heap,内存回收基本就是GC机制,而Android O上一部分在Java heap,一部分在Native heap,Java heap内存回收跟Android N上一致,Native heap的内存又是如何回收的呢?Android N上有没有需要回收的native内存呢?
其实,不管是Android N和Android O,或者其他版本,Bitmap的内存在native heap上都会有部分内存,整个内存分配过程主要动过JNI调用,这也是Bitmap.java的recycle的意义所在~而Android O上的native heap的内存释放,其实也是类似于Java的GC机制-引用计数,当没有引用指向其native heap数据时,回收内存~
验证
我在Bitmap申请内存时和回收内存时打印了部分log,简单看下实际运行的log,上面所说的就很明了了:
02-26 18:04:58.652 4602 4602 D BitmapFactory: lwl doDecode
02-26 18:04:58.652 4602 4602 D OpenGLRenderer: lwl allocateHeapBitmap 19872432
02-26 18:04:58.652 4602 4602 D OpenGLRenderer: lwl contruct Bitmap Heap
02-26 18:04:58.824 4602 4602 D Bitmap : lwl Bitmap.cpp createBitmap
点击back键后:
02-26 18:05:02.037 4602 4602 W OpenGLRenderer: lwl destroy Bitmap Heap
02-26 18:05:02.037 4602 4602 D OpenGLRenderer: lwl RenderProxy::onBitmapDestroyed 608
02-26 18:05:02.037 4602 4632 D OpenGLRenderer: lwl RenderState::onBitmapDestroyed 608
从log看,decode bitmap大小为19872432B,约19MB,lwl contruct Bitmap Heap 这句log是在Bitmap的构造方法中添加的,Heap代表的是Heap类型(共有Heap,External,Ashmem,Hardware四种类型),lwl Bitmap.cpp createBitmap标识在native层创建Bitmap过程,其中户执行Bitmap.java中的构造方法~
@@ -365,6 +366,7 @@ Bitmap::Bitmap(void* address, size_t size, const SkImageInfo& info, size_t rowBy
mPixelStorage.heap.address = address;
mPixelStorage.heap.size = size;
reconfigure(info, rowBytes, ctable);
+ ALOGD(“lwl contruct Bitmap Heap”);
}
而在析构Bitmap数据时,执行完free(mPixelStorage.heap.address);后再执行其他的清理操作~
Bitmap::~Bitmap() {
switch (mPixelStorageType) {
case PixelStorageType::External:
mPixelStorage.external.freeFunc(mPixelStorage.external.address,
mPixelStorage.external.context);
ALOGW(“lwl destroy Bitmap External”);
break;
case PixelStorageType::Ashmem:
munmap(mPixelStorage.ashmem.address, mPixelStorage.ashmem.size);
close(mPixelStorage.ashmem.fd);
ALOGW(“lwl destroy Bitmap Ashmem”);
break;
case PixelStorageType::Heap:
free(mPixelStorage.heap.address);
ALOGW(“lwl destroy Bitmap Heap”);
break;
case PixelStorageType::Hardware:
auto buffer = mPixelStorage.hardware.buffer;
buffer->decStrong(buffer);
mPixelStorage.hardware.buffer = nullptr;
ALOGW(“lwl destroy Bitmap Hardware”);
break;
}