Skip to content

Instantly share code, notes, and snippets.

@rinunu rinunu/gist:1046532
Created Jun 25, 2011

Embed
What would you like to do?
Android Bitmap and OutOfMemoryError
package nu.rinu;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Debug;
import android.util.Log;
/**
* Bitmap(ネイティブメモリ)のアロケートで OutOfMemoryError が発生する問題の調査
*
* <h4>現象</h4>
* <ul>
* <li>メモリが足りないにもかかわらず GC が発生しない場合があるように見える
* <li>メモリが足りないにもかかわらず SoftReference がクリアされない場合があるように見える
* <li>Bitmap のサイズが大きいほど発生しやすいように見える
* <li>GC を明示的に実行すると、許容できる Bitmap のサイズが大きくなる。 GC を複数回呼び出すと、さらに効果がある。
* <li>端末(や環境?)によって許容できる Bitmap のサイズがことなる(IS06 等は問題が表面化しない)。
* </ul>
*
*
* <h4>解決策</h4>
*
* <h5>妥協</h5>
*
* <ul>
* <li>1回で確保するネイティブメモリを小さくする
* <li>GC を明示的に実行する
* <li>SoftReference のキャッシュ数を少なくする
* <li>Bitmap.recycle() する。
* </ul>
*
* <h5>完全</h5>
* <ul>
* <li>SoftReference を使わない and (GC を明示的に実行する or Bitmap.recycle() する)
* </ul>
*
* <h5>メモ</h5> finalize が呼ばれた場合は問題が起きていないため、 Bitmap
* の開放に問題があるのではなく、開放判断に問題があると思われる TODO recycle
*
*/
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
private static final int LOOP_COUNT = 200;
private static interface Recyclable {
void recycle();
}
/**
* メモリを確保する
*/
private interface Allocator {
Object allocate(int no);
}
/**
* 参照を保持する
*
* <p>
* 参照の保持方法、参照を解除するタイミングはサブクラスが任意に選択する。
*/
private interface Cache {
void add(Object value, int no);
}
private interface Debaggable {
String getStatus();
}
// ----------
// 実装
private static class HeapObject implements Recyclable {
private int[] a;
public HeapObject(int width) {
a = new int[width * 1000];
}
@Override
public void recycle() {
}
}
private static class BitmapObject implements Recyclable {
private Bitmap bitmap;
private int no;
public BitmapObject(int no, int width) {
this.no = no;
bitmap = Bitmap.createBitmap(width, 1000, Bitmap.Config.ARGB_8888);
}
@Override
public void recycle() {
bitmap.recycle();
}
@Override
protected void finalize() throws Throwable {
Log.d(TAG, "finalize " + no);
}
}
// ----------
// Cache 実装
private static class MySoftReference<T> extends SoftReference<T> {
private int no;
public MySoftReference(T value, ReferenceQueue<T> referenceQueue, int no) {
super(value, referenceQueue);
this.no = no;
}
}
/**
* Reference で参照を一定件数保持する {@link Cache}
*/
private static abstract class AbstractCache implements Cache, Debaggable {
private List<Reference<Object>> list = new ArrayList<Reference<Object>>();
private Queue<Reference<Object>> queue = new LinkedList<Reference<Object>>();
private int max;
private boolean recycle;
public AbstractCache(int max, boolean recycle) {
this.max = max;
this.recycle = recycle;
}
public void add(Object value, int no) {
Reference<Object> ref = getReference(value, no);
list.add(ref);
queue.add(ref);
while (queue.size() > max) {
Reference<Object> refOld = queue.poll();
list.remove(refOld);
Object old = refOld.get();
if (old != null) {
if (recycle && old instanceof Recyclable) {
((Recyclable) old).recycle();
}
}
}
}
@Override
public String getStatus() {
int softRefCount = 0;
for (Reference<?> i : list) {
Object o = i.get();
if (o != null) {
++softRefCount;
}
}
return String.format(" refs %d", softRefCount);
}
protected abstract Reference<Object> getReference(Object object, int no);
}
/**
* SoftReference で参照を一定件数保持する {@link Cache}
*/
private static class SoftCache extends AbstractCache {
private ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
public SoftCache(int max, boolean recycle) {
super(max, recycle);
}
@Override
protected Reference<Object> getReference(Object object, int no) {
return new MySoftReference<Object>(object, referenceQueue, no);
}
}
/**
* WeakReference で参照を保持する {@link Cache}
*/
private static class WeakCache extends AbstractCache {
private ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
public WeakCache(int max, boolean recycle) {
super(max, recycle);
}
@Override
protected Reference<Object> getReference(Object object, int no) {
return new WeakReference<Object>(object, referenceQueue);
}
}
/**
* 参照を保持しない {@link Cache}
*/
private static class NullCache implements Cache {
public void add(Object value, int no) {
};
}
// ----------
// Allocator 実装
private static class BitmapObjectAllocator implements Allocator {
private int width;
public BitmapObjectAllocator(int width) {
this.width = width;
}
@Override
public Object allocate(int no) {
Log.d(TAG, "■ allocate");
return new BitmapObject(no, width);
}
};
private static class HeapAllocator implements Allocator {
private int width;
public HeapAllocator(int width) {
this.width = width;
}
@Override
public Object allocate(int no) {
Log.d(TAG, "■ allocate");
return new HeapObject(width);
}
};
// ----------
// 実行
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Debug.startAllocCounting();
final int width = 1100;
test(new BitmapObjectAllocator(width), new SoftCache(3, false), 0);
}
/**
* テストを実行する
*/
private <T> void test(Allocator allocator, Cache cache, int gcCount) {
for (int i = 0; i < LOOP_COUNT; ++i) {
printMemory(i, cache);
for (int j = 0; j < gcCount; ++j) {
gc();
}
Object memory = allocator.allocate(i);
cache.add(memory, i);
}
}
private static void printMemory(int no, Cache cache) {
Log.d(TAG,
String.format("■ %d native %d-%d / %d", no, Debug.getNativeHeapFreeSize(),
Debug.getNativeHeapAllocatedSize(), Debug.getNativeHeapSize()));
Log.d(TAG, String.format(" gc %d %d", Debug.getGlobalGcInvocationCount(), Debug.getThreadGcInvocationCount()));
if (cache instanceof Debaggable) {
Log.d(TAG, ((Debaggable) cache).getStatus());
}
}
private static void gc() {
Log.d(TAG, "■ gc");
System.gc();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.