Avoiding Running Out Of Memory

  • Thread starter Lawrence D'Oliveiro
  • Start date
L

Lawrence D'Oliveiro

I’ve implemented a simple picture picker widget in this Android app I’m
working on. Here’s the guts of the code that initializes it:

ThePics.Clear();
final String ExternalStorage =
android.os.Environment.getExternalStorageDirectory().getAbsolutePath();
final String[] Places =
{
ExternalStorage.concat("/Pictures"),
ExternalStorage.concat("/DCIM"),
};
for (String Place : Places)
{
final java.io.File ThisDir = new java.io.File(Place);
if (ThisDir.isDirectory())
{
for (java.io.File Item : ThisDir.listFiles())
{
android.graphics.Bitmap ThisImage =
android.graphics.BitmapFactory.decodeFile(Item.getAbsolutePath());
if (ThisImage != null)
{
ThePics.AppendItem(ThisImage, Item, Item.getName());
} /*if*/
} /*for*/
} /*if*/
} /*for*/
ThePics.notifyDataSetChanged();

The first time after the app was launched, bringing up the picker would
work. But on the second time, the app would crash with
“java.lang.OutOfMemoryError: bitmap size exceeds VM budget†in
android.graphics.BitmapFactory.decodeFile.

Even rotating the phone (which causes the re-creation of the activity)
triggered the crash.

Just to make it clear, the widget does NOT keep a reference to the Bitmap
that is passed; it creates a copy, scaled as necessary so as not to exceed a
maximum thumbnail size (160x160 pixels). So each thumbnail should take no
more than about 100K max, and my phone only has about a dozen pictures on
it.

I went over the “Avoiding Memory Leaks†article
<http://developer.android.com/resources/articles/avoiding-memory-leaks.html>,
looking carefully for unwanted references that might be preventing bitmaps
from being freed.

Finally, one simple change did the trick: after that ThePics.Append call
above, I added

ThisImage.recycle();

and that fixed it! Now I can bring up the picker multiple times during a
single app invocation, rotate the phone any number of times I like, and it
all works nicely.

So the question is: why was the garbage collector being so tardy in
reclaiming all those loaded full-size images?
 
A

Andreas Leitgeb

Lawrence D'Oliveiro said:
Finally, one simple change did the trick: after that ThePics.Append call
above, I added
ThisImage.recycle();
and that fixed it! Now I can bring up the picker multiple times during a
single app invocation, rotate the phone any number of times I like, and it
all works nicely.
So the question is: why was the garbage collector being so tardy in
reclaiming all those loaded full-size images?

I'd suspect, that during creation of that image object it (or some internal
meta-data about it) is internally added to some internal collection.
Calling .recycle() on the object would then remove it from that internal
collection, thusly removing the safety snatch that had immunized it against
the gc.
 
L

Lew

markspace said:
I don't see a type for ThisImage in your code, and the Java API doesn't seem
to have a recycle method. What API is this?

With a little help from my friend Google I found:
http://developer.android.com/reference/android/graphics/Bitmap.html
http://developer.android.com/reference/android/graphics/Bitmap.html#recycle()

which tells us its purpose is to
"Free the native object associated with this bitmap, and clear the reference
to the pixel data. This will not free the pixel data synchronously; it simply
allows it to be garbage collected if there are no other references."

Google also showed me that the use of 'Bitmap#recycle()' has turned up in the
OP's scenario before.

It's odd, though. The method's documentation also avers,
"This is an advanced call, and normally need not be called, since the normal
GC process will free up this memory when there are no more references to this
bitmap."

So the question is why that isn't true.
 
K

Kevin McMurtrie

Lawrence D'Oliveiro said:
I’ve implemented a simple picture picker widget in this Android app I’m
working on. Here’s the guts of the code that initializes it:

ThePics.Clear();
final String ExternalStorage =
android.os.Environment.getExternalStorageDirectory().getAbsolutePa
th();
final String[] Places =
{
ExternalStorage.concat("/Pictures"),
ExternalStorage.concat("/DCIM"),
};
for (String Place : Places)
{
final java.io.File ThisDir = new java.io.File(Place);
if (ThisDir.isDirectory())
{
for (java.io.File Item : ThisDir.listFiles())
{
android.graphics.Bitmap ThisImage =
android.graphics.BitmapFactory.decodeFile(Item.getAbso
lutePath());
if (ThisImage != null)
{
ThePics.AppendItem(ThisImage, Item, Item.getName());
} /*if*/
} /*for*/
} /*if*/
} /*for*/
ThePics.notifyDataSetChanged();

The first time after the app was launched, bringing up the picker would
work. But on the second time, the app would crash with
“java.lang.OutOfMemoryError: bitmap size exceeds VM budget†in
android.graphics.BitmapFactory.decodeFile.

Even rotating the phone (which causes the re-creation of the activity)
triggered the crash.

Just to make it clear, the widget does NOT keep a reference to the Bitmap
that is passed; it creates a copy, scaled as necessary so as not to exceed a
maximum thumbnail size (160x160 pixels). So each thumbnail should take no
more than about 100K max, and my phone only has about a dozen pictures on
it.

I went over the “Avoiding Memory Leaks†article
<http://developer.android.com/resources/articles/avoiding-memory-leaks.html>,
looking carefully for unwanted references that might be preventing bitmaps
from being freed.

Finally, one simple change did the trick: after that ThePics.Append call
above, I added

ThisImage.recycle();

and that fixed it! Now I can bring up the picker multiple times during a
single app invocation, rotate the phone any number of times I like, and it
all works nicely.

So the question is: why was the garbage collector being so tardy in
reclaiming all those loaded full-size images?

Native memory held by Java objects is freed by Finalizer threads or
system graphics threads watching a ReferenceQueue. It's one step behind
collections. Since the JVM can't track native memory, GC may give up on
the belief that additional collection cycles are futile.

If you can access the image as a raster, you may be able to perform
simple 1/n scaling before performing a resample to an exact size. For
example, scale 4368x2912 by 1/10 to 437x291 then resample to 160x107.
It's possible to perform anti-aliased resampling on a raster too but
it's very heavy in floating point math.
 
R

Roedy Green

ThisImage.recycle();

similarly watch for any "dispose" methods. They are needed to free
resources in the OS outside Java.
--
Roedy Green Canadian Mind Products
http://mindprod.com
Refactor early. If you procrastinate, you will have
even more code to adjust based on the faulty design.
..
 
L

Lew

Roedy said:
"Lawrence D'Oliveiro" wrote, quoted or indirectly quoted someone who said :
similarly watch for any "dispose" methods. They are needed to free
resources in the OS outside Java.

In this context, 'recycle()' performs the actions of a 'dispose()' in another
framework. Others upthread, notably Kevin McMurtrie, have elucidated further
details of what you've correctly identified for the OP as a resource problem.

The posted code's readability is harmed somewhat by its flouting of coding
conventions.
 
L

Lawrence D'Oliveiro

Kevin McMurtrie said:
Since the JVM can't track native memory, GC may give up on
the belief that additional collection cycles are futile.

Yeah, I gathered something of the sort from other related postings I found.

I think this is a point in favour of adding reference counting as a first
resort, before falling back on garbage collection. This is how other
languages like Perl and Python do it.
If you can access the image as a raster, you may be able to perform
simple 1/n scaling before performing a resample to an exact size.

You mean, do my own decoding? That would mean giving up on BitmapFactory,
and restricting myself in the formats I can import.
 
L

Lawrence D'Oliveiro

similarly watch for any "dispose" methods. They are needed to free
resources in the OS outside Java.

But the API docs seem to say that recycle isn’t even necessary in normal
use.
 
A

Andreas Leitgeb

Lawrence D'Oliveiro said:
But the API docs seem to say that recycle isn’t even necessary in normal
use.

Seems like their definition of "normal use" didn't include your
particular use. Maybe it's just some coincidence that calling
recycle() even helped you, and fixing something else somewhere
else would have been necessary to qualify for that API-docs'
sense of "normal use".
 
J

Joshua Cranmer

Yeah, I gathered something of the sort from other related postings I found.

I think this is a point in favour of adding reference counting as a first
resort, before falling back on garbage collection. This is how other
languages like Perl and Python do it.

I don't think that would help, since the native memory that holds it
probably would need to have a reference. The purpose of not collecting
it is to make sure that the native memory doesn't think it has a valid
reference if it doesn't; changing the collection scheme wouldn't solve that.
 
K

Kevin McMurtrie

Lawrence D'Oliveiro said:
Yeah, I gathered something of the sort from other related postings I found.

I think this is a point in favour of adding reference counting as a first
resort, before falling back on garbage collection. This is how other
languages like Perl and Python do it.

It's not about how the garbage is found. It's about the process of
freeing it possibly taking multiple steps.
You mean, do my own decoding? That would mean giving up on BitmapFactory,
and restricting myself in the formats I can import.

Some decoders can produce a raster. JPEG is a fat raster as long as
it's not progressive. GIF and PNG are a raster, though maybe with
interleaving. There are C libraries that will downsample and decode in
one step but I haven't seen those algorithms show up in Java. JPEG can
be scaled extremely efficiently because it's already stored as frequency
data.
 
L

Lawrence D'Oliveiro

Maybe it's just some coincidence that calling recycle() even helped you,

ldo@theon:jigsaw_puzzle_maker> git show HEAD
commit 6544a312bfca4983c3420a5bacab5454ee4ecc0c
Author: Lawrence D'Oliveiro <[email protected]_zealand>
Date: Sun Mar 6 10:34:46 2011 +0000

fix running out of memory on reinvocations of picture picker

diff --git a/src/nz/gen/geek_central/JigsawPuzzleMaker/PicturePicker.java b/src/nz/gen/geek_central/
index e180f1c..b10a441 100644
--- a/src/nz/gen/geek_central/JigsawPuzzleMaker/PicturePicker.java
+++ b/src/nz/gen/geek_central/JigsawPuzzleMaker/PicturePicker.java
@@ -117,6 +117,7 @@ public class PicturePicker extends android.app.Activity
if (ThisImage != null)
{
ThePics.AppendItem(ThisImage, Item, Item.getName());
+ ThisImage.recycle(); /* try to avoid memory leaks */
} /*if*/
} /*for*/
} /*if*/

As you can see, it was the one and only change.
 
L

Lawrence D'Oliveiro

Kevin McMurtrie said:
It's not about how the garbage is found. It's about the process of
freeing it possibly taking multiple steps.

Doesn’t matter how many steps it takes. With reference-counting, you get to
free it the moment the last reference is gone.
Some decoders can produce a raster. JPEG is a fat raster as long as
it's not progressive. GIF and PNG are a raster, though maybe with
interleaving. There are C libraries that will downsample and decode in
one step but I haven't seen those algorithms show up in Java. JPEG can
be scaled extremely efficiently because it's already stored as frequency
data.

Can’t be bothered.

There is plenty of RAM on the phone to cope with one full-size image at a
time, and that’s all I need.
 
L

Lawrence D'Oliveiro

I don't think that would help, since the native memory that holds it
probably would need to have a reference. The purpose of not collecting
it is to make sure that the native memory doesn't think it has a valid
reference if it doesn't; changing the collection scheme wouldn't solve
that.

The pixels aren’t being shared, so there’s no problem there.

If they were being shared, reference-counting would help.
 
A

Andreas Leitgeb

Lawrence D'Oliveiro said:
As you can see, it was the one and only change.

Seems like you misunderstood me completely. But then again, it wasn't
really all that important, anyway, so please just forget what I said.
 
L

Lew

bugbear said:
There's a distinction between what that doc says, and taking steps to
ensure that all references to the bitmap are null'd out or destroyed
as soon as possible.

That's just it. The docs say that normal use should suffice for that, but I
think they ignore the question of out-of-JVM resources (native). It's well
known that nuliing a reference is generally unnecessary (with notable
exceptions) and can even hinder rather than help GC (according to Brian
Goetz). All that's usually needed is for references to go out of scope, the
preferred way to facilitate GC.

The normal "steps to ensure that all references" die include knowing what
references you keep, in order to avoid packratting.
I imagine the recycle call is intended to facilitate the latter.

I conclude that the purpose is more to facilitate release of native resources,
based on the docs and the answers from others in this thread.
 
L

Lawrence D'Oliveiro

Andreas Leitgeb said:
Seems like you misunderstood me completely.

This isn’t Microsoft Windows. Things don’t mysteriously misbehave just
because you squint at them wrong.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,074
Latest member
StanleyFra

Latest Threads

Top