What does Bitmap#recycle() in Android Honeycomb actually DO? - android

I am writing a very memory intensive application for Android Honeycomb, and I've been very careful to recycle() unused Bitmaps wherever possible; indeed, this is necessary for the application to work at all, as Bitmaps are constantly being cycled in and out of memory. However, I have just implemented onConfigurationChanged() in the Activity, and so (for a number of reasons) I am trying to put memory freeing routines in onStop().
Currently my onStop() method:
sets some Views to display a default Drawable;
calls recycle() on the Bitmaps previously used by these Views;
nulls references to the Bitmaps.
Unfortunately, using the Eclipse memory profiler, it seems this is having no effect on the memory usage at all.
As you can imagine, having made so much effort to free resources in a nominally garbage-collected language, I would have hoped for a little more effect. So my question is: what does recycle() do? Does it actually trigger garbage collection, or will the system hold on to the memory—even if you call System.gc()—until it feels the need to get rid of something?
NB I know Bitmaps aren't actually held in the regular heap but I thought calling recycle() was enough to ensure they were dropped out of the native heap.
PART OF THE ANSWER
I have discovered that if an ImageView contains a Bitmap that has been recycled, the Bitmap data is still retained in memory until setImageBitmap(null) is called on the ImageView. This may even be the case if setImageResource(...) or setImageDrawable(...) are called (they were, loading in a relatively small nine-patch—however, MAT analysis shows this did not remove the large Bitmap, which was contained in the private members of the ImageView). Simply calling this function at onStop() has culled about 10MB from the heap of our application. Apparently this may not be the case for pre-Honeycomb builds of Android, though.

As Justin says, Bitmap data is not allocated in the VM heap. There is a reference to it in the VM heap (which is small), but the actual data is allocated in the Native heap by the underlying Skia graphics library. [Note that this may have changed in later Android levels, but is true for 2.1 and 2.2]
When you do a recycle() that marks both the small portion in the VM heap and the actual data in the native heap as free and available for GC. But the actual collection is performed by two different GC mechanisms. The portion in the VM heap is collected by the Davlik GC - and you can see that happening via DDMS. But the native heap data is collected by the Skia GC, which appears to be lazier (it runs less frequently?). That means that, even with rigorous recycle()s, it is possible to get ahead of the native heap GC.
Fortunately there are mechanisms to monitor the state of the native heap. See BitmapFactory OOM driving me nuts.

I have discovered that, in Honeycomb onwards, if an ImageView contains a Bitmap that has been recycled, the Bitmap data is still retained in memory until setImageBitmap(null) is called on the ImageView. This may even be the case if setImageResource(...) or setImageDrawable(...) are called (in this case, a very large bitmap was replaced with a fairly small nine-patch, but only when setImageBitmap(null) was called before loading the nine-patch was the memory actually disposed).

Recycle frees the native memory that is allocated to the bitmap. The actual Bitmap object will remain in the Dalvik Heap until the next garbage collection (but the memory taken up by this object is insignificant).
As far as I am aware, there really is no way to dump the native heap. So you won't be able to see if the bitmap's native data is gone via a heap dump. You should see, however, the total amount of memory your application is using go down. This question should help you discover the various ways to access the memory usage stats of your app.

Related

Android Running apps memory usage

What is the difference between the heap usage (Allocated) we can see in the Elipse Memory Analysis Tool (in the DDMS view) and the memory usage size for the same App shown here on the Android device?:
Settings->Apps->Running
Even though I aggressively tried to preserve memory by making objects null as soon as they weren't needed, the latter number (memory usage size on Running apps screen) only kept increasing and my app finally crashed due to OutOfMemoryError. However, the former showed me that I was well within a reasonable size. I was also calling System.gc() a lot. Is there a difference between the two? Why the discrepancy? Any ideas on how I can solve this problem?
The biggest difference between the two that I know of is the scope of garbage collection.
Normal garbage collection, including System.gc(), collects a bit of garbage, then stops. It is not a complete sweep of the heap to get rid of everything. That is to try to minimize the CPU impact of garbage collection.
The heap dump prepared for MAT, though, effectively a complete GC.
Your symptoms suggest that you are allocating memory faster than GC can reclaim it. The primary solution for this is to try to allocate less memory, or allocate it less frequently. For example, where possible, reuse objects, bitmap buffers, and the like, instead of trying to let GC clean the old stuff and allocating new stuff as you go.
It sounds like you have a memory leak somewhere in your application if the memory is never released. This means that somewhere you are maintaining a strong reference to a large object which is being recreated (like an Activity or Bitmap) which is why calling System.gc() is making no difference.
I suggest watching the following on memory management in android from google IO 2011. It lets you know how to use the eclipse memory analyser tool which is incredibly useful for debugging this sort of error

Android, I see heap growing, but I want it to stop

I see my heap growing and I know it will eventually crash on any device since it just keeps growing.
Grow heap (frag case)
is seen throughout the logs
on my phone it will crash the app at 32mb used. Other phones will of course be 16mb, if there are any with that few resources that run android 2.2
Now, I am using recycle() on my bitmaps, setting things to null, popping items from complex data structures, and using System.gc() to invoke garbage collection, throughout the app
but, the heap still grows and its a problem... eventually
How do I just force the app to dump resources so that it can continue functioning.
it is usually always the "bitmap vm budget" exceeding, but I am feeling more and more that I just don't have access to the "clear bitmap vm" command
i had also struggled a lot with this issue. there are many a things you can do.
you can call recycle on each bitmap and set them to null.(bitmap.recycle() with relaese all the memory used by this bitmap but does not nullify the bitmap object ).
you can also unbind the drawables associated with layouts as specified in this link.
you can convert your hashmaps to WeakHashmaps. so that its memory would get relaesed when system runs low on memory.
you can resize all your bitmaps. have a look at this link..
you can override onLowMemory() method in activity which gets a call when entire system runs low on memory. you can release few resources there.
you can make your objects SoftReference or Weakreference, so that they get released in low memory condition.
But the real fact is that, all this can DELAY your out of memory issue/crash, but can not eliminate it because thing is that you must be leaking your activity context or memory somewhere.

Using caching to improve scrolling performance in an android ListView with images

I'm currently using a filesystem cache to cache my images as I download them from the server. I have a ListView that contains custom views, each of which retrieves its image from the filesystem when getView() is called.
To improve performance, I implemented a java.util.WeakHashMap<String,Bitmap> that stores each of the bitmaps by a unique key. This allows me to tuck the images into the hashmap as they're downloaded, and then retrieve them directly from memory to populate my listview. This avoids a file I/O operation and results in a much smoother scrolling experience.
The idea is that as the OS runs low on memory, it will clean out the WeakHashMap to free up memory.
However, this doesn't work on Android 2.3 or earlier. The problem is that bitmaps are not kept in the Java Heap, and are instead kept in native memory. This means that the JVM garbage collector has no idea how much memory those images are occupying, and thus never bothers to free them up when the OS is low on native memory, resulting in OutOfMemory errors when there's plenty of memory that can still be reclaimed.
This has been fixed in Android 3.0, since 3.0 stores bitmaps in JVM heap instead of native memory, but the question is how can I easily cache bitmaps on Android 2.3 and later without inadvertently causing unnecessary OutOfMemory exceptions?
You might try something like this: http://code.google.com/p/xlarge-demos/source/browse/trunk/PhotoAlbum/src/com/example/android/photoalbum/LruCache.java and explicitly recycle() your Bitmaps in the evicted step.
So the answer seems to be that there's a bug in the way the dalvik VM detects when it needs to do a GC pass. If you manually call System.gc() immediately before allocating memory for your bitmap, the OutOfMemory errors surprisingly go away.
if(Build.VERSION.SDK_INT < 12) {
Log.d("Running garbage collection for Bitmaps");
System.gc();
}
return BitmapFactory.decodeStream(is);
Obviously, the VM should be doing this GC automatically before it throws an OutOfMemory, but it does not appear to do so.
So, even though Bitmap memory is allocated on the native heap in earlier versions of Android, this memory is still charged against your process, its just harder to see, this is why you might get an OOM Exception. However, your basic analysis is correct though. The problem is that the native code doesn't really have a good idea when it can deallocate memory for Bitmaps, which is why its recommended that developers all Bitmap.recycle(), since this essentially tells native code that its okay to free the memory. Likely when items are removed from the WeakHashMap, this isn't being called.
However, empirically I'd built a similar system using a HashMap<String, SoftReference<Bitmap>> and Bitmap memory was properly freed. I'll note though that I think this solution became less effective starting in Android 2.3 because of changes to the garbage collector, although I'd need to go back and verify this remembrance.
In the end I guess the answer is that I don't know of a good answer to this question that doesn't use explicit management like the LruCache. It would be great to have a solution that uses SoftReferences or WeakReferences, but with the current way we do garbage collection I'm not sure this will work.

Bitmap, Bitmap.recycle(), WeakReferences, and Garbage Collection

AFAIK on Android, it is recommended to reference Bitmap objects as WeakReferences in order to avoid memory leaks. When no more hard references are kept of a bitmap object, the garbage collector will automatically collect it.
Now, if I understand correctly, the method Bitmap.recycle() must always be called to free a Bitmap. I think this is because Bitmap objects have special memory management.
Is that correct?
If this is true, when using WeakReferences, there must be memory leaks because Bitmap.recycle() is never called when the WeakReferences are freed. Or, somehow, are WeakReferences sufficient to avoid memory leaks?
Thanks
Bitmap.recycle isn't required to be called, as the garbage collector will clean up bitmaps on its own eventually (as long as there are no references). Bitmaps in Android are created in native memory, not on the VM heap, so the actual bitmap object on the VM heap is very small as it doesn't contain any actual bitmap data. (EDIT: no longer the case as of Android 3.0+) The real size of the bitmap will still be counted against your heap usage for purposes of GC and making sure your app doesn't use too much memory.
However, the GC seems to be a little moody when it comes to Bitmaps. If you just remove all hard references, it would sometimes (in my case) hang onto the Bitmaps for a little while longer, perhaps because of the weird way Bitmap objects are allocated/counted. Bitmap.recycle seems to be good for getting the GC to collect that object more quickly.
Either way, you won't leak memory if you don't call Bitmap.recycle as long as you don't keep hard references accidentally. You may encounter OutOfMemoryErrors if you try to allocate too many bitmaps at once or too large bitmaps without calling recycle, though.
EDIT: It is important to note that as of Android 3.0, Bitmaps are no longer allocated in native memory. The are allocated on the VM heap like any other Java object. However, what I said about not needing to call recycle still applies.

BitmapFactory OOM driving me nuts

I've been doing a lot of searching and I know a lot of other people
are experiencing the same OOM memory problems with BitmapFactory. My
app only shows a total memory available of 4MB using Runtime.getRuntime
().totalMemory(). If the limit is 16MB, then why doesn't the total
memory grow to make room for the bitmap? Instead it throws an error.
I also don't understand that if I have 1.6MB of free memory according
to Runtime.getRuntime().freeMemory() why do I get an error saying "VM
won't let us allocate 614400 bytes"? Seems to me I have plenty
available memory.
My app is complete except for this problem, which goes away when I
reboot the phone so that my app is the only thing running. I'm using
an HTC Hero for device testing (Android 1.5).
At this point I'm thinking the only way around this is to somehow
avoid using BitmapFactory.
Anyone have any ideas on this or an explanation as to why VM won't
allocate 614KB when there's 1.6MB of free memory?
[Note that (as CommonsWare points out below) the whole approach in this answer only applies up to and including 2.3.x (Gingerbread). As of Honeycomb Bitmap data is allocated in the VM heap.]
Bitmap data is not allocated in the VM heap. There is a reference to it in the VM heap (which is small), but the actual data is allocated in the Native heap by the underlying Skia graphics library.
Unfortunately, while the definition of BitmapFactory.decode...() says that it returns null if the image data could not be decoded, the Skia implementation (or rather the JNI glue between the Java code and Skia) logs the message you’re seeing ("VM won't let us allocate xxxx bytes") and then throws an OutOfMemory exception with the misleading message "bitmap size exceeds VM budget".
The issue is not in the VM heap but is rather in the Native heap. The Natïve heap is shared between running applications, so the amount of free space depends on what other applications are running and their bitmap usage. But, given that BitmapFactory will not return, you need a way to figure out if the call is going to succeed before you make it.
There are routines to monitor the size of the Native heap (see the Debug class getNative methods). However, I have found that getNativeHeapFreeSize() and getNativeHeapSize() are not reliable. So in one of my applications that dynamically creates a large number of bitmaps I do the following.
The Native heap size varies by platform. So at startup, we check the maximum allowed VM heap size to determine the maximum allowed Native heap size. [The magic numbers were determined by testing on 2.1 and 2.2, and may be different on other API levels.]
long mMaxVmHeap = Runtime.getRuntime().maxMemory()/1024;
long mMaxNativeHeap = 16*1024;
if (mMaxVmHeap == 16*1024)
mMaxNativeHeap = 16*1024;
else if (mMaxVmHeap == 24*1024)
mMaxNativeHeap = 24*1024;
else
Log.w(TAG, "Unrecognized VM heap size = " + mMaxVmHeap);
Then each time we need to call BitmapFactory we precede the call by a check of the form.
long sizeReqd = bitmapWidth * bitmapHeight * targetBpp / 8;
long allocNativeHeap = Debug.getNativeHeapAllocatedSize();
if ((sizeReqd + allocNativeHeap + heapPad) >= mMaxNativeHeap)
{
// Do not call BitmapFactory…
}
Note that the heapPad is a magic number to allow for the fact that a) the reporting of Native heap size is "soft" and b) we want to leave some space in the Native heap for other applications. We are running with a 3*1024*1024 (ie 3Mbytes) pad currently.
1.6 MB of memory seems like a lot but it could be the case that the memory is so badly fragmented that it can't allocate such big block of memory in one go (still this does sound very strange).
One common cause of OOM while using image resources is when one is decompressing JPG, PNG, GIF images with really high resolutions. You need to bear in mind that all these formats are pretty well compressed and take up very little space but once you load the images to the phone, the memory they're going to use is something like width * height * 4 bytes. Also, when decompression kicks in, a few other auxiliary data structures need to be loaded for the decoding step.
It seems like the issues given in Torid's answer have been resolved in the more recent versions of Android.
However, if you are using an image cache (a specialized one or even just a regular HashMap), it is pretty easy to get this error by creating a memory leak.
In my experience, if you inadvertently hold on to your Bitmap references and create a memory leak, OP's error (an referring to the BitmapFactory and native methods) is the one that will crash your app (up to ICS - 14 and +?)
To avoid this, make your you "let go" of your Bitmaps. This means using SoftReferences in the final tier of your cache, so that Bitmaps can get garbage collected out of it. This should work, but if you are still getting crashes, you can try to explicitly mark certain Bitmaps for collection by using bitmap.recycle(), just remember to never return a bitmap for use in your app if bitmap.isRecycled().
As an aside, LinkedHashMaps are a great tool for easily implementing pretty good cache structures, especially if you combine hard and soft references like in this example (starting line 308)... but using hard references is also how you can get yourself into memory leak situations if you mess up.
Although usually it doesnt make sense to catch an Error because usually they are thrown only by the vm but in this particular case the Error is thrown by the jni glue code thus it is very simple to handle cases where you could not load the image: just catch the OutOfMemoryError.
Although this is a fairly high level answer, the problem for me turned out to be using hardware acceleration on all of my views. Most of my views have custom Bitmap manipulation, which I figured to be the source of the large native heap size, but in fact when disabling hardware acceleration the native heap usage was cut down by a factor of 4.
It seems as though hardware acceleration will do all kinds of caching on your views, creating bitmaps of its own, and since all bitmaps share the native heap, the allocation size can grow pretty dramatically.

Categories

Resources