Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android GC memory fragmentation fail. Workaround?

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

like image 744
Brandyn Avatar asked May 21 '12 08:05

Brandyn


2 Answers

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

like image 52
Carl Avatar answered Nov 05 '22 18:11

Carl


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.

like image 22
dronus Avatar answered Nov 05 '22 19:11

dronus