19

I am testing on android 3.1, large heapsize option, about 250M of memory available.

I set the following code to be run whenever I hit a Test button in my app's prefs:

float [][][]foo = new float[3][2048][2048];
Bitmap bm = Bitmap.createBitmap(2048, 2048, Bitmap.Config.ARGB_8888);
bm.recycle();
bm  = null;
foo = null;

I have plenty of memory for this -- I can hit the button a few times without problem.

But if I keep hitting the button, eventually (less than 20 hits) it dies with OutOfMemory. [Usually in android.graphics.Bitmap.nativeCreate(Native Method)]

Nothing else is going on -- I never have to leave the PreferencesActivity. There is a small Toast that is also displayed when I hit the button, so a tiny amount of other UI activity is going on.

Is this due to fragmentation, or just a horrible bug in the android Bitmap code and/or GC? Or am I just doing something stupid? (Please let it be something stupid...)

Does anybody have a workaround? Because the above is fairly representative of what my code has to do each time the user invokes it, and right now despite meticulous clearing of variables it dies after a few uses. (And this has been driving me nuts for a long time now!)

[Update]

I have confirmed it's a fragmentation issue or gc bug, as a heap dump shows I'm only using 5.6M when idle (no leaks) peaking at about 26M during processing. (Also, native heap stays below 4M.) While the java heap meanwhile grows in extent all the way to the 280M limit on my test device at which point I start getting OutOfMemory exceptions. So I am only using 10% of my available heap at peak, but getting OutOfMemory.

[Adding a call to System.gc() unfortunately fixes the simple test case I give above. I say unfortunate because (A) it shouldn't make a difference, and (B) because I already call it regularly in my real code so it means my simple test case above is too simple.]

Has anyone else run into this? Any workarounds? Is there a graceful way to restart my app?

[Update]

The following version reliably causes OutOfMemory in 3 to 4 invocations (presses of the button):

float [][][]foo = new float[3][2048][2048];
Bitmap bm = Bitmap.createBitmap(2048, 2048, Bitmap.Config.ARGB_8888);
int []bar = new int[3*2048*2048];
bm.recycle();
bm = null;
System.gc();
foo = null;
System.gc();
bar = null;
System.gc();

Memory tracing shows the heap growing steadily each invocation until it hits the limit and dies. If I remove any one of the three allocations, it reaches equilibrium and survives indefinitely. Removing all but the last gc() causes it to die slightly sooner.

I would say this is a fragmentation issue, not a gc bug per se. If anybody knows how to fix it, let me know. The int[] allocation is for writing a Bitmap so I do not have the option of allocating it as a 2d array (limitation of the android Bitmap library).

Brandyn
  • 430
  • 3
  • 9
  • I don't know if this will help, but dalvik uses a [Mark-and-Sweep](http://www.brpreiss.com/books/opus5/html/page424.html#SECTION0014300000000000000000) type GC. Where it will try to wait till all available memory is used, then clean it out all at once (performance issues, because it needs to stop processing to GC). Perhaps the `System.gc();` call is exactly what is needed with this problem, because the GC is expecting smaller objects. – Ancantus May 21 '12 at 13:28
  • 1
    @Ancantus - Unfortunately, as mentioned, I call System.gc() after every recycle()/null in my real code and still get this behavior. I am thinking it's a fragmentation problem or something like System.gc() being ignored or behaving differently in background threads. I would be willing to kill and restart my app from scratch in order to compact the memory if there were a reliable way to do that. I would also like a call to tell me, post-GC, how much of the java heap is free space (which would tell me immediately and for sure if it is a fragmentation problem or obscure leak). Is there one? – Brandyn May 21 '12 at 19:14
  • Yah as you probably know calling `System.gc();` just flags the GC to start, it doesn't necessarily mean it will run at all. Actually I was doing a search around for GC bitmap related problems, [this SO question](http://stackoverflow.com/questions/7852943/what-does-bitmaprecycle-in-android-honeycomb-actually-do) might help. – Ancantus May 21 '12 at 20:12
  • 1
    @Ancantus - Fwiw, I added a finalizer with debug message to my main memory-allocating object and it is in fact finalized when I call System.gc(). In fact all of my gc appears to be working perfectly in terms of no leaks and even cleaning things up at key points mid-process to keep peak usage to a minimum. But all of this is only evident to heap-debugging tools like MAT. The android device meanwhile grows the java heap by 20-50M a shot until it dies. WTF?? – Brandyn May 21 '12 at 23:19
  • Is the float array neccessary to trigger the crash? Perhaps it's an issue with multi-dimensional arrays? What if you make it 1d with 3*2k*2k elements? – Stefan Haustein May 21 '12 at 23:37
  • @Brandyn Well, I am thoroughly stumped, going to try it myself when I get the chance. – Ancantus May 22 '12 at 00:10
  • @StefanHaustein - Good questions, and I am going to play with various combos and see if I can narrow it down more. Unfortunately I still don't have a true distilled test case since System.gc() fixes the simple one above, and my real code has a number of different float allocations of varying sizes... – Brandyn May 22 '12 at 04:12
  • @StefanHaustein, see updated description. – Brandyn May 23 '12 at 22:49
  • 1
    Did you try with float []foo = new float[3*2048*2048]? I tend to agree that this is some kind of fragmentation issue. Is there a maximum image size, i.e. could you allocate the arrays and a bitmap for the maximum size and then keep them around all the time instead of re-allocating? – Stefan Haustein May 24 '12 at 00:16
  • @StefanHaustein, sorry, forgot to try that. Just did, and it's same behavior (dies after 3-4 passes), which is kinda surprising since I figured it was an interaction of the various sized parts... – Brandyn May 24 '12 at 03:49
  • You are using bitmap, you have to rescale your images, So that they would not take much memory – Umer Abid Feb 21 '13 at 12:37
  • http://stackoverflow.com/questions/5321579/out-of-memory-exception-due-to-large-bitmap-size Try this – Umer Abid Feb 21 '13 at 12:45
  • This SO comment may be of interest: "NEVER use Bitmap.createBitmap(width, height, Config.ARGB_8888). I mean NEVER! I've never had that thing not raise memory error after few passes. No amount of recycle(), System.gc(), whatever helped. It always raised exception." http://stackoverflow.com/questions/477572/strange-out-of-memory-issue-while-loading-an-image-to-a-bitmap-object – Carl Apr 06 '13 at 14:21
  • The same commenter also adds: "The one other way that actually works is to have a dummy image in your drawables (or another Bitmap that you decoded using step 1 above), rescale that to whatever you want, then manipulate the resulting Bitmap (such as passing it on to a Canvas for more fun). So, what you should use instead is: Bitmap.createScaledBitmap(srcBitmap, width, height, false). If for whatever reason you MUST use the brute force create method, then at least pass Config.ARGB_4444." – Carl Apr 06 '13 at 14:21
  • And, finally, the grateful response from the OP: "BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; and Bitmap.createScaledBitmap(srcBitmap, width, height, false); solved my issue I had with out of memory exception on android 4.0.0. Thanks mate!" – Carl Apr 06 '13 at 14:23

3 Answers3

4

Here is another SO page that apparently has a workaround for this problem:

Strange out of memory issue while loading an image to a Bitmap object

Specifically, the answer by Ephraim (excerpted):

"1) Every time you do BitmapFactory.decodeXYZ(), make sure to pass in a BitmapFactory.Options with inPurgeable set to true (and preferably with inInputShareable also set to true).

"2) NEVER use Bitmap.createBitmap(width, height, Config.ARGB_8888). I mean NEVER! I've never had that thing not raise memory error after few passes. No amount of recycle(), System.gc(), whatever helped. It always raised exception. The one other way that actually works is to have a dummy image in your drawables (or another Bitmap that you decoded using step 1 above), rescale that to whatever you want, then manipulate the resulting Bitmap (such as passing it on to a Canvas for more fun). So, what you should use instead is: Bitmap.createScaledBitmap(srcBitmap, width, height, false). If for whatever reason you MUST use the brute force create method, then at least pass Config.ARGB_4444."

In comments, some people said that this solved their problem, which is very similar to that of the OP here.

I would add that 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. That may make your native heap figures irrelevant. See hackbod's comment on this page:

Bitmaps in Android

I guess that implies a fairly major change as of Honeycomb regarding bitmap allocation, and so that could explain why there are bugs with such allocations (if there are). I don't know what effect this change has on the recycle() command, but in light of the above comments by Ephraim the answer may be "not a very good one."

Finally,

To use largeHeap to ingest huge bitmaps could be seen as not playing nice with other apps, especially if you are going close to the physical limits of the device. I'm not sure how you can avoid that, but be prepared for a lot of onPause() / onResume() activity as your app steps on other apps, and they step back on yours. This SO answer includes a discussion of this:

Detect application heap size in Android

Community
  • 1
  • 1
Carl
  • 15,445
  • 5
  • 55
  • 53
1

To prevent fragmentation, you could just allocate the large array AND the Bitmap once and reuse it.

For Android, there is some caveats to this, as Android tries to manage your App's ressources to some degree. For example, Activity's or View's may be unloaded if not visible, and re-run later if they became visible again. So the large things should better be stored by an Application object or a static place.

If this is just used for some preference dialog, you should reserve it on the first use but keep it afterwards, to not use that much memory at every run. If it is used rarely, you maybe should restart your application after the preferences screen is left. The user does not need to take notice of it if made well, and you would get a fresh and memory friendly process again.

dronus
  • 10,774
  • 8
  • 54
  • 80
  • Not an option unfortunately. This is an image processing application that takes user-supplied images of various sizes (with no hard-cap max -- max is whatever can squeeze into available memory). – Brandyn Feb 16 '13 at 21:26
  • Even with no static array and Bitnaos you can still try to restart the application after one or some images. Maybe it's enough to quit the last `Activity` by `finish()` and reopen it via `startActivity`. However the VM may be reused in a 'smart' way, so it may be necessary to run a helper service on exit, that would restart your main `Activity` again. – dronus Feb 16 '13 at 21:50
  • @Brandyn In 2016 the problem with memory fragmentation is still present. I started from using LruCache but after a couple of months of usage I see that it causes OutOfMemory due to memory fragmentation. See https://github.com/andstatus/andstatus/issues/351 The only solution that I want to try now is to allocate image cache once and then reuse cached bitmaps memory. I will have to allocate bitmaps of maximum size in order to be reusable... – yvolk Apr 04 '16 at 07:01
-2

There is a very detailed article about managing bitmap memory on android developer.android.com:

http://developer.android.com/training/displaying-bitmaps/manage-memory.html

Basically they recommend to use Bitmap.recycle() in order to avoid OutOfMemoryError errors for Android 2.3.3 and lower. According to the documentation it frees the native object associated with the Bitmap Object.

nanoquack
  • 949
  • 2
  • 9
  • 26