Bitmap内存分配以及回收

Bitmap内存分配

  • Android 2.3.3 (API level 10)以及更早的Android版本中,Bitmap的像素数据保存在native内存中,像素数据内存的回收则在finalize()中进行回收,存在很大的不确定性,很容易导致OOM的发生;
  • 从Android 3.0 (API level 11) 到Android 7.1 (API level 25),像素数据存放在Java Heap中,跟Bitmap对象一起回收。但由于图片是内存消耗大户,所以也很容易导致OOM,以及频繁的GC导致内存抖动问题。
  • 在Android 8.0 (API level 26)以及更高的版本中,像素数据保存在native heap中。通过一个辅助类NativeAllocationRegistry来实现native内存的回收。

在android.graphics.BitmapFactory类中有一系列函数可以解码生成Bitmap:

  • Bitmap decodeByteArray(byte[] data, int offset, int length)
  • Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)
  • Bitmap decodeFile(String pathName)
  • Bitmap decodeFile(String pathName, Options opts)
  • …… 等等

这些方法都会调用到jni层frameworks\base\libs\hwui\jni\BitmapFactory.cpp,最终都会走到doDecode方法:

Android 12位图解码,使用native内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//frameworks\base\libs\hwui\jni\BitmapFactory.cpp
static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
jobject padding, jobject options, jlong inBitmapHandle,
jlong colorSpaceHandle) {
...... //代码省略
// Scale is necessary due to density differences.
if (scale != 1.0f) {
willScale = true;
scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f);
scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f);
}

android::Bitmap* reuseBitmap = nullptr;
unsigned int existingBufferSize = 0;
if (javaBitmap != nullptr) {
reuseBitmap = &bitmap::toBitmap(inBitmapHandle);
if (reuseBitmap->isImmutable()) {
ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
javaBitmap = nullptr;
reuseBitmap = nullptr;
} else {
existingBufferSize = reuseBitmap->getAllocationByteCount();
}
}

HeapAllocator defaultAllocator;
RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize);
ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
SkBitmap::HeapAllocator heapAllocator;
SkBitmap::Allocator* decodeAllocator;
if (javaBitmap != nullptr && willScale) {
// This will allocate pixels using a HeapAllocator, since there will be an extra
// scaling step that copies these pixels into Java memory. This allocator
// also checks that the recycled javaBitmap is large enough.
decodeAllocator = &scaleCheckingAllocator;
} else if (javaBitmap != nullptr) {
decodeAllocator = &recyclingAllocator;
} else if (willScale || isHardware) {
// This will allocate pixels using a HeapAllocator,
// for scale case: there will be an extra scaling step.
// for hardware case: there will be extra swizzling & upload to gralloc step.
decodeAllocator = &heapAllocator;
} else {
decodeAllocator = &defaultAllocator;
}
......
SkBitmap decodingBitmap; //SkBitmap 是skia库中的类
//decodingBitmap.tryAllocPixels的作用是分配piexel内存,最终调用的是
//decodeAllocator->allocPixelRef(this);
if (!decodingBitmap.setInfo(bitmapInfo) ||
!decodingBitmap.tryAllocPixels(decodeAllocator)) {
return nullptr;
}

// Use SkAndroidCodec to perform the decode.
SkAndroidCodec::AndroidOptions codecOptions;
codecOptions.fZeroInitialized = decodeAllocator == &defaultAllocator ?
SkCodec::kYes_ZeroInitialized : SkCodec::kNo_ZeroInitialized;
codecOptions.fSampleSize = sampleSize;
//codec指的是SkAndroidCodec,是skia库中的类。下面这句代码就是真正的解码了
SkCodec::Result result = codec->getAndroidPixels(decodeInfo, decodingBitmap.getPixels(),
decodingBitmap.rowBytes(), &codecOptions);

......
//按比例缩放位图
SkBitmap outputBitmap;
if (willScale) {
// Set the allocator for the outputBitmap.
SkBitmap::Allocator* outputAllocator;
if (javaBitmap != nullptr) {
outputAllocator = &recyclingAllocator;
} else {
outputAllocator = &defaultAllocator;
}

SkColorType scaledColorType = decodingBitmap.colorType();
// FIXME: If the alphaType is kUnpremul and the image has alpha, the
// colors may not be correct, since Skia does not yet support drawing
// to/from unpremultiplied bitmaps.
outputBitmap.setInfo(
bitmapInfo.makeWH(scaledWidth, scaledHeight).makeColorType(scaledColorType));
if (!outputBitmap.tryAllocPixels(outputAllocator)) {
// This should only fail on OOM. The recyclingAllocator should have
// enough memory since we check this before decoding using the
// scaleCheckingAllocator.
return nullObjectReturn("allocation failed for scaled bitmap");
}

SkPaint paint;
// kSrc_Mode instructs us to overwrite the uninitialized pixels in
// outputBitmap. Otherwise we would blend by default, which is not
// what we want.
paint.setBlendMode(SkBlendMode::kSrc);

SkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy);
canvas.scale(scaleX, scaleY);
decodingBitmap.setImmutable(); // so .asImage() doesn't make a copy
canvas.drawImage(decodingBitmap.asImage(), 0.0f, 0.0f,
SkSamplingOptions(SkFilterMode::kLinear), &paint);
} else {
outputBitmap.swap(decodingBitmap);
}

......
//硬件位图
if (isHardware) {
//GPU中分配位图内存。原outputBitmap是否需要释放呢?在哪释放?
sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(outputBitmap);
if (!hardwareBitmap.get()) {
return nullObjectReturn("Failed to allocate a hardware bitmap");
}
//创建java层Bitmap,此处的hardwareBitmap.release()不是资源回收的意思,而是取出sk_sp中的Bitmap实例。
return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags,
ninePatchChunk, ninePatchInsets, -1);
}

// now create the java bitmap
return bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(),
bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
}

默认pixel内存分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
bool HeapAllocator::allocPixelRef(SkBitmap* bitmap) {
mStorage = android::Bitmap::allocateHeapBitmap(bitmap);
return !!mStorage;
}
//frameworks\base\libs\hwui\hwui\Bitmap.cpp
sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap) {
return allocateBitmap(bitmap, &Bitmap::allocateHeapBitmap);
}
static sk_sp<Bitmap> allocateBitmap(SkBitmap* bitmap, AllocPixelRef alloc) {
const SkImageInfo& info = bitmap->info();
if (info.colorType() == kUnknown_SkColorType) {
LOG_ALWAYS_FATAL("unknown bitmap configuration");
return nullptr;
}

size_t size;

// we must respect the rowBytes value already set on the bitmap instead of
// attempting to compute our own.
const size_t rowBytes = bitmap->rowBytes();
if (!Bitmap::computeAllocationSize(rowBytes, bitmap->height(), &size)) {
return nullptr;
}

auto wrapper = alloc(size, info, rowBytes); //使用入参的alloc函数分配内存
if (wrapper) {
wrapper->getSkBitmap(bitmap);
}
return wrapper;
}
sk_sp<Bitmap> Bitmap::allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) {
void* addr = calloc(size, 1); //调用Linux C库函数分配内存
if (!addr) {
return nullptr;
}
return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes)); //返回最终的Bitmap对象
}

比例缩放或硬件位图场景内存分配

在需要将图片进行按比例缩放,或者使用硬件内存时,使用SkBitmap::HeapAllocator来分配内存。当解码位图时如果配置了android.graphics.Bitmap.Config#HARDWARE就会使用硬件位图。硬件位图是一种 Android O 添加的新的位图格式。硬件位图仅在显存 (graphic memory) 里存储像素数据,并对图片仅在屏幕上绘制的场景做了优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//external\skia\src\core\SkBitmap.cpp
bool SkBitmap::HeapAllocator::allocPixelRef(SkBitmap* dst) {
const SkImageInfo& info = dst->info();
if (kUnknown_SkColorType == info.colorType()) {
return false;
}
sk_sp<SkPixelRef> pr = SkMallocPixelRef::MakeAllocate(info, dst->rowBytes());
if (!pr) {
return false;
}
dst->setPixelRef(std::move(pr), 0, 0);
return true;
}
//external\skia\src\core\SkMallocPixelRef.cpp
sk_sp<SkPixelRef> SkMallocPixelRef::MakeAllocate(const SkImageInfo& info, size_t rowBytes) {
if (rowBytes == 0) {
rowBytes = info.minRowBytes();
// rowBytes can still be zero, if it overflowed (width * bytesPerPixel > size_t)
// or if colortype is unknown
}
if (!is_valid(info) || !info.validRowBytes(rowBytes)) {
return nullptr;
}
size_t size = info.computeByteSize(rowBytes);
if (SkImageInfo::ByteSizeOverflowed(size)) {
return nullptr;
}
#if defined(SK_BUILD_FOR_FUZZER)
if (size > 100000) {
return nullptr;
}
#endif
void* addr = sk_calloc_canfail(size);
if (nullptr == addr) {
return nullptr;
}

struct PixelRef final : public SkPixelRef {
PixelRef(int w, int h, void* s, size_t r) : SkPixelRef(w, h, s, r) {}
~PixelRef() override { sk_free(this->pixels()); }
};
return sk_sp<SkPixelRef>(new PixelRef(info.width(), info.height(), addr, rowBytes));
}
//external\skia\include\private\SkMalloc.h
static inline void* sk_calloc_canfail(size_t size) {
#if defined(SK_BUILD_FOR_FUZZER)
// To reduce the chance of OOM, pretend we can't allocate more than 200kb.
if (size > 200000) {
return nullptr;
}
#endif
return sk_malloc_flags(size, SK_MALLOC_ZERO_INITIALIZE);
}
//external\skia\src\ports\SkMemory_malloc.cpp
void* sk_malloc_flags(size_t size, unsigned flags) {
void* p;
if (flags & SK_MALLOC_ZERO_INITIALIZE) {
p = calloc(size, 1);
} else {
#if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) && defined(__BIONIC__)
/* TODO: After b/169449588 is fixed, we will want to change this to restore
* original behavior instead of always disabling the flag.
* TODO: After b/158870657 is fixed and scudo is used globally, we can assert when an
* an error is returned.
*/
// malloc() generally doesn't initialize its memory and that's a huge security hole,
// so Android has replaced its malloc() with one that zeros memory,
// but that's a huge performance hit for HWUI, so turn it back off again.
(void)mallopt(M_THREAD_DISABLE_MEM_INIT, 1);
#endif
p = malloc(size);
#if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) && defined(__BIONIC__)
(void)mallopt(M_THREAD_DISABLE_MEM_INIT, 0);
#endif
}
if (flags & SK_MALLOC_THROW) {
return throw_on_failure(size, p);
} else {
return p;
}
}

Android 7使用Java内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//frameworks\base\core\jni\android\graphics\BitmapFactory.cpp
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
...... //代码省略

//JavaPixelAllocator就是从Java heap中分配内存,JavaPixelAllocator中通过
//使用dalvik.system.VMRuntime.getRuntime().newNonMovableArray来申请内存
JavaPixelAllocator javaAllocator(env);
//复用inBitmap
RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize);
//ScaleCheckingAllocator内部通过SkBitmap::HeapAllocator来申请native内存,但是
//这个内存只是临时内存,在后面的scale操作时会最终申请java内存,native内存会被释放
ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
//SkBitmap::HeapAllocator是skia库提供的内存分派类,使用native内存
SkBitmap::HeapAllocator heapAllocator;
SkBitmap::Allocator* decodeAllocator;
if (javaBitmap != nullptr && willScale) {
// This will allocate pixels using a HeapAllocator, since there will be an extra
// scaling step that copies these pixels into Java memory. This allocator
// also checks that the recycled javaBitmap is large enough.
decodeAllocator = &scaleCheckingAllocator;
} else if (javaBitmap != nullptr) {
decodeAllocator = &recyclingAllocator;
} else if (willScale) {
// This will allocate pixels using a HeapAllocator, since there will be an extra
// scaling step that copies these pixels into Java memory.
decodeAllocator = &heapAllocator;
} else {
decodeAllocator = &javaAllocator;
}
......
SkBitmap outputBitmap;
if (willScale) {
// This is weird so let me explain: we could use the scale parameter
// directly, but for historical reasons this is how the corresponding
// Dalvik code has always behaved. We simply recreate the behavior here.
// The result is slightly different from simply using scale because of
// the 0.5f rounding bias applied when computing the target image size
const float sx = scaledWidth / float(decodingBitmap.width());
const float sy = scaledHeight / float(decodingBitmap.height());

// Set the allocator for the outputBitmap.
SkBitmap::Allocator* outputAllocator;
if (javaBitmap != nullptr) {
outputAllocator = &recyclingAllocator; //复用inBitmap
} else {
outputAllocator = &javaAllocator; //使用Java内存
}

SkColorType scaledColorType = colorTypeForScaledOutput(decodingBitmap.colorType());
// FIXME: If the alphaType is kUnpremul and the image has alpha, the
// colors may not be correct, since Skia does not yet support drawing
// to/from unpremultiplied bitmaps.
outputBitmap.setInfo(SkImageInfo::Make(scaledWidth, scaledHeight,
scaledColorType, decodingBitmap.alphaType()));
if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) {
// This should only fail on OOM. The recyclingAllocator should have
// enough memory since we check this before decoding using the
// scaleCheckingAllocator.
return nullObjectReturn("allocation failed for scaled bitmap");
}

SkPaint paint;
// kSrc_Mode instructs us to overwrite the unininitialized pixels in
// outputBitmap. Otherwise we would blend by default, which is not
// what we want.
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
paint.setFilterQuality(kLow_SkFilterQuality);

SkCanvas canvas(outputBitmap);
canvas.scale(sx, sy);
canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
} else {
outputBitmap.swap(decodingBitmap);
}
......

Android4.4使用Java内存,支持共享内存

SkImageDecoderSkJPEGImageDecoderSkPNGImageDecoderSkGIFImageDecoderSkBMPImageDecoderSkPixelRefSkImageRefSkImageRef_ashmembool onDecode()AshmemAllocatorbool allocPixelRef(SkBitmap* bm, SkColorTable* ct)SkBitmapSkMallocPixelRefAndroidPixelRef

Bitmap内存回收机制

从Android 8.0开始,Bitmap的pixels数据放在native heap中,通过NativeAllocationRegistry来回收内存。这一节分析NativeAllocationRegistry的原理。

NativeAllocationRegistry内存回收原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//android-12.1.0_r27\frameworks\base\graphics\java\android\graphics\Bitmap.java
Bitmap(long nativeBitmap, int width, int height, int density,
boolean requestPremultiplied, byte[] ninePatchChunk,
NinePatch.InsetStruct ninePatchInsets, boolean fromMalloc) {
...... //代码省略
mNativePtr = nativeBitmap;

final int allocationByteCount = getAllocationByteCount();
NativeAllocationRegistry registry;
if (fromMalloc) { //内存在native堆中进行分配
registry = NativeAllocationRegistry.createMalloced(
Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
} else { //fromMalloc=false是内存在Ashmem,或者gpu上进行分配的场景
registry = NativeAllocationRegistry.createNonmalloced(
Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
}
registry.registerNativeAllocation(this, nativeBitmap);
}
private static native long nativeGetNativeFinalizer();

NativeAllocationRegistry的使用比较简单,通过createMalloced/createNonmalloced来构造实例,然后调用registerNativeAllocation来进行注册。构造函数一共有3个参数,第一个是ClassLoader,第二个是nativeGetNativeFinalizer,第三个是内存大小。ClassLoader和内存大小很好理解,nativeGetNativeFinalizer是对应什么呢?它是一个jni函数,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//android\android-12.1.0_r27\frameworks\base\libs\hwui\jni\Bitmap.cpp
static const JNINativeMethod gBitmapMethods[] = {
......
{ "nativeGetNativeFinalizer", "()J", (void*)Bitmap_getNativeFinalizer },
......
}

static void Bitmap_destruct(BitmapWrapper* bitmap) {
delete bitmap; //最终会调用到析构函数Bitmap::~Bitmap()
}

static jlong Bitmap_getNativeFinalizer(JNIEnv*, jobject) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Bitmap_destruct));
}

nativeGetNativeFinalizer就是native函数Bitmap_destruct的函数指针,Bitmap_destruct中会调用到native层Bitmap类的析构函数,从而释放内存。

继续看下NativeAllocationRegistry的构造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static NativeAllocationRegistry createNonmalloced(
@NonNull ClassLoader classLoader, long freeFunction, long size) {
return new NativeAllocationRegistry(classLoader, freeFunction, size, false);
}

public static NativeAllocationRegistry createMalloced(
@NonNull ClassLoader classLoader, long freeFunction, long size) {
return new NativeAllocationRegistry(classLoader, freeFunction, size, true);
}

private NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size,
boolean mallocAllocation) {
if (size < 0) {
throw new IllegalArgumentException("Invalid native allocation size: " + size);
}
this.classLoader = classLoader;
this.freeFunction = freeFunction;
this.size = mallocAllocation ? (size | IS_MALLOCED) : (size & ~IS_MALLOCED);
}

可以看出构造函数只是给成员变量赋值。真正的工作是在registerNativeAllocation中进行的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//android-12.1.0_r27\libcore\luni\src\main\java\libcore\util\NativeAllocationRegistry.java
public @NonNull Runnable registerNativeAllocation(@NonNull Object referent, long nativePtr) {
......
CleanerThunk thunk;
CleanerRunner result;
try {
thunk = new CleanerThunk();
Cleaner cleaner = Cleaner.create(referent, thunk); //对应sun.misc.Cleaner
result = new CleanerRunner(cleaner); //忽略这一行,因为Bitmap调用registerNativeAllocation没有使用返回值
registerNativeAllocation(this.size);
} catch (VirtualMachineError vme /* probably OutOfMemoryError */) {
applyFreeFunction(freeFunction, nativePtr);
throw vme;
} // Other exceptions are impossible.
// Enable the cleaner only after we can no longer throw anything, including OOME.
thunk.setNativePtr(nativePtr);
// Ensure that cleaner doesn't get invoked before we enable it.
Reference.reachabilityFence(referent);
return result;
}

private class CleanerThunk implements Runnable {
private long nativePtr;
public CleanerThunk() { this.nativePtr = 0; }

public void run() {
if (nativePtr != 0) {
applyFreeFunction(freeFunction, nativePtr); //回收内存
registerNativeFree(size);
}
}

public void setNativePtr(long nativePtr) { this.nativePtr = nativePtr; }
}
//jni函数,执行native内存回收
public static native void applyFreeFunction(long freeFunction, long nativePtr);

看来NativeAllocationRegistry是通过sun.misc.Cleaner来执行内存回收的。

Cleaner源码分析

(源码分析基于jdk1.8.0)

首先, Cleaner是一个虚引用,继承自PhantomReference,如下图:

ReferenceObjectPhantomReferenceObjectCleaner

第二: Cleaner的父类Reference的静态代码块中会启动一个线程ReferenceHandler,这个线程永远不会退出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public abstract class Reference<T> {
private static class ReferenceHandler extends Thread {
......
public void run() {
while (true) { //while循环永远不会退出
tryHandlePending(true);
}
}
}
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, "Reference Handler");
handler.setPriority(Thread.MAX_PRIORITY); //高优先级
handler.setDaemon(true);
handler.start();

// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}

第三: pending是一个Reference链表,由GC来赋值。pending链表中的Reference都在等待被加入到ReferenceQueue中,如果Reference没有关联到ReferenceQueue,就不会被添加到pending列表。而ReferenceHandler是一个永远不会退出的线程,一直从链表中获取元素,当元素是Cleaner类型时就会执行Cleaner.clean()方法,否则就会加入到ReferenceQueue队列中。

1
2
3
4
5
6
/* List of References waiting to be enqueued.  The collector adds
* References to this list, while the Reference-handler thread removes
* them. This list is protected by the above lock object. The
* list uses the discovered field to link its elements.
*/
private static Reference<Object> pending = null;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
// 'instanceof' might throw OutOfMemoryError sometimes
// so do this before un-linking 'r' from the 'pending' chain...
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink 'r' from 'pending' chain
pending = r.discovered; //pending链表指向下一个Reference
r.discovered = null;
} else {
// The waiting on the lock may cause an OutOfMemoryError
// because it may try to allocate exception objects.
if (waitForNotify) {
lock.wait(); //pending为空,线程阻塞等待。
//当GC每次执行收集流程之前都会获取lock,然后将被回收的Reference放到pending链表中,然后唤醒阻塞线程
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
// Give other threads CPU time so they hopefully drop some live references
// and GC reclaims some space.
// Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
// persistently throws OOME for some time...
Thread.yield();
// retry
return true;
} catch (InterruptedException x) {
// retry
return true;
}

// Fast path for cleaners
if (c != null) {
c.clean(); //执行Cleaner的clean()方法
return true;
}

ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r); //将其他Reference加入到ReferenceQueue中。
return true;
}

参考文章

硬件加速
硬件位图
Android硬件位图填坑之获取硬件画布