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.

Andrew Wyld
  • 7,133
  • 7
  • 54
  • 96
  • I have. I actually stepped through the code that recycles them and checked `isRecycled()` in the debugger and it returned `true`. – Andrew Wyld Oct 21 '11 at 17:40
  • 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? – Andrew Wyld Oct 21 '11 at 17:44

3 Answers3

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.

Community
  • 1
  • 1
Torid
  • 4,176
  • 1
  • 28
  • 29
  • I've already implemented some things to get round native heap problems (including an `AsyncTask` to load `Bitmap`s 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 .... – Andrew Wyld Oct 21 '11 at 18:31
  • 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()`). – Andrew Wyld Oct 24 '11 at 10:25
  • 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. – Justin Breitfeller Oct 24 '11 at 14:32
  • 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 necessary – Torid Oct 24 '11 at 17:47
  • 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. – Andrew Wyld Nov 29 '11 at 12:49
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).

Andrew Wyld
  • 7,133
  • 7
  • 54
  • 96
  • 1
    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. – Justin Breitfeller Oct 24 '11 at 14:27
  • I've taken out the reference to System.gc()—and yes, we're running Honeycomb. I'll just edit the post to explain that. – Andrew Wyld Oct 25 '11 at 09:45
  • 3
    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... – Adrian Feb 16 '12 at 11:57
  • Whoa. I haven't worked with ICS yet so I didn't know this. Does this still crash if isRecycled() returns false? – Andrew Wyld Feb 21 '12 at 18:35
  • 2
    @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. – cottonBallPaws Apr 01 '12 at 23:12
  • This is some pretty wild information. Do you guys have references to official documentation on this? – Maarten Dec 24 '12 at 19:10
  • 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. – Andrew Wyld Dec 25 '12 at 10:03
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.

Community
  • 1
  • 1
Justin Breitfeller
  • 13,737
  • 4
  • 39
  • 47
  • 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 `View`s? If so, how do I free it from the `View`s? – Andrew Wyld Oct 21 '11 at 17:31
  • 1
    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. – Justin Breitfeller Oct 21 '11 at 17:38
  • 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 `ImageView`s containing `Bitmap`s, 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!) – Andrew Wyld Oct 21 '11 at 17:43
  • 1
    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. – Justin Breitfeller Oct 21 '11 at 17:46
  • No, I tried that and it stays there. The MAT seems like a good bet, thank you :) – Andrew Wyld Oct 21 '11 at 17:52
  • Ah. "Unknown HPROF Version (JAVA PROFILE 1.0.3)". – Andrew Wyld Oct 21 '11 at 18:06
  • http://stackoverflow.com/questions/6219049/error-openning-hprof-file solves this :) – Andrew Wyld Oct 21 '11 at 18:07
  • 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. – Andrew Wyld Oct 21 '11 at 18:20
  • `setImageBitmap(null)` seems to be doing something! Who knew? – Andrew Wyld Oct 21 '11 at 18:28
  • 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. – Andrew Wyld Oct 24 '11 at 10:20
  • PS thank you @Justin Breitfeller for pointing out the MAT tool :) – Andrew Wyld Oct 24 '11 at 10:23
  • 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: http://stackoverflow.com/questions/1945142/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: http://stackoverflow.com/questions/477572/strange-out-of-memory-issue-while-loading-an-image-to-a-bitmap-object – Carl Apr 06 '13 at 14:49