Created
June 25, 2011 14:17
-
-
Save rinunu/1046532 to your computer and use it in GitHub Desktop.
Android Bitmap and OutOfMemoryError
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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