Using SoftReference to cache Bitmap on Android cause OOM

5.2k Views Asked by At

I'm developing an application that need to load Bitmap. And using a SoftReference for the cache. I relate every soft reference with a ReferenceQueue and using a hash map to access the SoftReference . As following shows:

public static class MemCache {

    final private ReferenceQueue<Bitmap> queue = new ReferenceQueue<Bitmap>();
    private Map<String, SoftReference<Bitmap>> hash = null;

    public MemCache() {
        hash = Collections.synchronizedMap(
            new LinkedHashMap<String, SoftReference<Bitmap>>()
        );
    }

    public synchronized Bitmap put(String key, Bitmap value) {
        clean();
        SoftReference<Bitmap> ref = new SoftReference<Bitmap>(value, queue);
        SoftReference<Bitmap> res = hash.put(key, ref);
        if (res == null) return null;
        return res.get();
    }

    public synchronized Bitmap get(Object key) {
        clean();
        SoftReference<Bitmap> ref = hash.get(key);
        if (ref == null) return null;
        Bitmap val = ref.get();
        if (val != null) return val;
        hash.remove(key);
        return null;
    }
}

Then, when I write the clean() like :

    private synchronized void clean() {
        Reference<? extends Bitmap> sv;
        while ((sv = queue.poll()) != null)
            hash.remove(sv);
        Queue<String> toRemove = new LinkedList<String>();
        for(Entry<String, SoftReference<Bitmap>> e : hash.entrySet()){
            if(e.getValue()==null) continue;
            if(e.getValue().get()==null) 
                toRemove.add(e.getKey());
        }
        String s;
        while((s = toRemove.poll())!= null)
            hash.remove(s);
        Log.e("ImageCacheManager", "MemCache Size/2:" + new String(new char[hash.size() / 2]).replace("\0", "="));
    }

Which check all the SoftReferences in the hash table for not null. The memcache appears good, but if I only wrote:

    private synchronized void clean() {
        Reference<? extends Bitmap> sv;
        while ((sv = queue.poll()) != null)
            hash.remove(sv);
        Log.e("ImageCacheManager", "MemCache Size/2:" + new String(new char[hash.size() / 2]).replace("\0", "="));
    }

Which only remove the element been put into ReferenceQueue The Log then will print more and more =, even there is some decrease, the tendency is increase

As what is mentioned in http://www.ibm.com/developerworks/library/j-refs/

The referent of the SoftReference is set to null. But most of the SoftReference was not in ReferenceQueue. Just between the state that the object is marked as finalizable but not be finalized? Will the Bitmap marked as finalizable but not be finalized been recycled?

1

There are 1 best solutions below

6
On BEST ANSWER

I have experimented a similar issue. After sometime I had realized that is because of the way android manage bitmap. If I have not misunderstood they use "skia", a native implementation, for bitmap. So bitmaps are not allocated in the java heap but in the native heap and the java bitmap object itself is really small and a poor candidate for GC. So they provided the recycle method that free the native memory retained by a bitmap.

Sorry for my poor english.