I encountered a strange problem when I was using soft reference on Android. I implemented a class for bitmap cache, the source code is as follows:
public class ImageCache
{
private static HashMap<String, SoftReference<Bitmap>> mCache = new HashMap<String, SoftReference<Bitmap>>();
private static final String TAG = "ImageCache";
public static Bitmap getBitmap(String url)
{
Bitmap bitmap = null;
if (mCache.containsKey(url))
{
Log.d(TAG, "use cache: " + url);
bitmap = mCache.get(url).get();
if (bitmap != null)
{
return bitmap;
}
else
{
Log.w(TAG, "#######################soft ref was collected!!!");
}
}
bitmap = BitmapFactory.decodeFile(url);
if (bitmap == null)
{
Log.e(TAG, "#####jpg not found");
return null;
}
bitmap = Bitmap.createScaledBitmap(bitmap, 320, 240, false);
synchronized (mCache) {
mCache.put(url, new SoftReference<Bitmap>(bitmap));
}
return bitmap;
}
}
But I found through the logcat that soft reference is collected frequently. The log is:
#######################soft ref was collected!!!
As far as I know, soft reference will be collected by GC only if java heap grow to its limit and there is no space for a new memory allocation.
But why soft reference on Android does not behave as expected?
This is incorrect.
According to Oracle documentation, any given SoftReference may be collected anytime, if the GC makes decision to do so. There is even VM parameter, called
-XX:SoftRefLRUPolicyMSPerMB
. So SoftReferences are meant to get cleared before having to increase heap size even on desktop JVMs (see also this question for some extra details on the matter).Android documentation gives even less guarantees, and clearly warns, that the VM won't actually insist on keeping those references around for long:
Which I personally read as "until next
GC_FOR_ALLOC
".There are certainly some valid uses for SoftReferences, such as making them a circuit breaker. The linked article also explains, why caching is not one of them. If you want to manage your cache in manner, that actually matters, use memory-limited
LruCache
and clear that fromonLowMemory()
. Or, better yet, just let go of Bitmaps after using them and let the OS decide, what to cache and when to destroy your application and it's caches.