What does Bitmap#recycle() in Android Honeycomb actually DO?
Asked Answered
L

3

15

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.

Lenee answered 21/10, 2011 at 17:19 Comment(2)
I have. I actually stepped through the code that recycles them and checked isRecycled() in the debugger and it returned true.Lenee
Oh I see what you mean, sorry. (Long day.) What I meant was, does the memory get freed now or sometime in the future, when Android feels like it?Lenee
L
6

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).

Lenee answered 24/10, 2011 at 10:18 Comment(7)
Andrew, are you using Honeycomb or ICS to do your development? It would be helpful to other SOers reading this question because it does make a significant difference. The behavior you are describing I would expect to only see in Honeycomb or later. Also, please see Android documentation about calling System.gc(). Its never a good idea to recommend calling that function. The GC almost always knows the absolute best time to run.Incommode
I've taken out the reference to System.gc()—and yes, we're running Honeycomb. I'll just edit the post to explain that.Lenee
One important note: explicitly calling recycle() on Bitmaps in ICS can lead to fatal crashes. Typical - you must recycle on earlier releases to avoid memory leakage and you mustn't recycle on ICS onwards to avoid crashes. Thanks, Google...Religious
Whoa. I haven't worked with ICS yet so I didn't know this. Does this still crash if isRecycled() returns false?Lenee
@Adrian, do you have any more details about that? I have an app running on many different ICS devices with recycle() used and haven't seen crashes. If you have some info about that I would love to see it to make sure I'm not doing something dangerous.Avram
This is some pretty wild information. Do you guys have references to official documentation on this?Pu
I don't but if anyone else reading this does I would be fascinated to read it! This is all found out by a combination of experiment and polling others' expertise.Lenee
Z
6

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.

Zinazinah answered 21/10, 2011 at 18:23 Comment(5)
I've already implemented some things to get round native heap problems (including an AsyncTask to load Bitmaps that catches OutOfMemoryError from the BitmapFactory and waits up to 2 seconds to try again) but in THIS case the memory seems to be located inside the ImageView objects. Calling setImageBitmap(null) AS WELL AS setImageResource(resourceID), mad though it sounds, is doing some good ....Lenee
A couple of further points: the problem here was not that the memory wasn't being recycled quickly (I already had wait loops dealing with this) but rather that it wasn't being recycled ever. As I have now discovered, the ImageView was keeping the Bitmap memory alive; calling setImageBitmap(null) cured the problem (amazingly, calling setImageDrawable(...) or setImageResource(...) does not appear to be sufficient to remove the Bitmap from memory—the explicit null has already culled about 10MB from the profile at onStop()).Lenee
Do you have any documentation about this Skia graphic garbage collection? I have never heard / read anything about this so it would be good information to know. Its a native library so it just seems extremely unlikely there would be any type of garbage collection there.Incommode
You're right. Skia is a native (C++) library, and has its own heap management laid over the stdlib memory management. Calling that a "GC" was a bit sloppy on my part. As to documentation, I'm not aware of any; I have just been code-reading where necessaryZinazinah
The recycle() method is SUPPOSED to dispose of the native Bitmap memory but as the question stated, it wasn't. I've also discovered that onStop() is not a good place to call recycling or bitmap-nulling code from because of the order of execution of methods for activities when you start a new one, which is Activity1.onPause(), Activity2.onCreate(), Activity2.onStart(), Activity2.onResume(), Activity1.onStop() which means the memory doesn't actually get freed if you free it from onStop() if another activity has been started.Lenee
L
6

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).

Lenee answered 24/10, 2011 at 10:18 Comment(7)
Andrew, are you using Honeycomb or ICS to do your development? It would be helpful to other SOers reading this question because it does make a significant difference. The behavior you are describing I would expect to only see in Honeycomb or later. Also, please see Android documentation about calling System.gc(). Its never a good idea to recommend calling that function. The GC almost always knows the absolute best time to run.Incommode
I've taken out the reference to System.gc()—and yes, we're running Honeycomb. I'll just edit the post to explain that.Lenee
One important note: explicitly calling recycle() on Bitmaps in ICS can lead to fatal crashes. Typical - you must recycle on earlier releases to avoid memory leakage and you mustn't recycle on ICS onwards to avoid crashes. Thanks, Google...Religious
Whoa. I haven't worked with ICS yet so I didn't know this. Does this still crash if isRecycled() returns false?Lenee
@Adrian, do you have any more details about that? I have an app running on many different ICS devices with recycle() used and haven't seen crashes. If you have some info about that I would love to see it to make sure I'm not doing something dangerous.Avram
This is some pretty wild information. Do you guys have references to official documentation on this?Pu
I don't but if anyone else reading this does I would be fascinated to read it! This is all found out by a combination of experiment and polling others' expertise.Lenee
I
3

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.

Incommode answered 21/10, 2011 at 17:27 Comment(12)
That's a good point actually ... memory usage on the Dalvik heap is rising as I run this, in proportion with the amount I would expect for the Bitmap native objects, so presumably this Dalvik usage is internal in Views? If so, how do I free it from the Views?Lenee
Its hard for me to say why your memory usage is increasing because it could be due to a number of things. Also, the garbage collector doesn't run constantly, so the only time you really have to worry is if after a GC, the number doesn't go down a significant amount. If you are using a tablet with Honeycomb or an ICS emulator, then the bitmap memory should be shown in the dalvik heap.Incommode
That's exactly what I am worrying about! I know what you mean, it's obviously impossible for you to know what else I am up to, but really it's just ImageViews containing Bitmaps, all of which come from BitmapFactory and BitmapRegionDecoder. DDMS verifies that most of the usage comes from there (each Bitmap tends to be about 3MB). recycle() is working sometimes or the thing would crash (like I said ... a LOT of cycling in and out!)Lenee
After a recycle call, if you force a GC via the DDMS tab, does the Bitmap get removed from your heap? If not, you can use the Eclipse MAT (memory analyzer tool) to see what object is keeping the bitmap alive.Incommode
No, I tried that and it stays there. The MAT seems like a good bet, thank you :)Lenee
Ah. "Unknown HPROF Version (JAVA PROFILE 1.0.3)".Lenee
https://mcmap.net/q/179541/-error-opening-hprof-file-ioexception-unknown-hprof-version solves this :)Lenee
Right, it appears that the ImageView itself contains a Bitmap and that is what is occupying the memory. This is after I have called setImageResource(R.drawable.smallishdrawable) so I had assumed it would remove the Bitmap, but I'm now trying setImageBitmap(null) as well.Lenee
setImageBitmap(null) seems to be doing something! Who knew?Lenee
Having tested a bit more thoroughly I can verify that setImageBitmap(null) has a measurable impact on system memory: with the call, the Bitmap memory is disposed, and without it, it is retained, despite calls to recycle(). I would recommend that anyone else having this problem do both.Lenee
PS thank you @Justin Breitfeller for pointing out the MAT tool :)Lenee
Diane Hackborn has commented that as of 3.0 Android no longer allocates bitmaps from the native heap but instead directly allocates them from the regular heap. See her comment on this page: https://mcmap.net/q/27159/-bitmaps-in-android I don't know how that might affect the recycle() command, or how recycle() might interact with the regular GC, but by some reports the answer could be "not in a very good way." See answer by Ephraim here: #478072Frankfurter

© 2022 - 2024 — McMap. All rights reserved.