java.lang.OutOfMemoryError: bitmap size exceeds VM budget - Android
Asked Answered
J

13

160

I developed an application that uses lots of images on Android.

The app runs once, fills the information on the screen (Layouts, Listviews, Textviews, ImageViews, etc) and user reads the information.

There is no animation, no special effects or anything that can fill the memory. Sometimes the drawables can change. Some are android resources and some are files saved in a folder in the SDCARD.

Then the user quits (the onDestroy method is executed and app stays in memory by the VM ) and then at some point the user enters again.

Each time the user enters to the app, I can see the memory growing more and more until user gets the java.lang.OutOfMemoryError.

So what is the best/correct way to handle many images?

Should I put them in static methods so they are not loaded all the time? Do I have to clean the layout or the images used in the layout in a special way?

Jerriejerrilee answered 22/12, 2009 at 20:51 Comment(1)
This may help if you have lots of drawables changing. It is working since I did the program myself :) androidactivity.wordpress.com/2011/09/24/…Mcnamee
T
73

It sounds like you have a memory leak. The problem isn't handling many images, it's that your images aren't getting deallocated when your activity is destroyed.

It's difficult to say why this is without looking at your code. However, this article has some tips that might help:

http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html

In particular, using static variables is likely to make things worse, not better. You might need to add code that removes callbacks when your application redraws -- but again, there's not enough information here to say for sure.

Tswana answered 22/12, 2009 at 21:55 Comment(2)
this is true and also having lots of images (and not a memory leak) can cause outOfMemory for multiple reasons like lots of other apps are running, fragmentation of memory, etc. I learned that when working with lots of images if you get the outOfMemory systematically, like doing always the same, then its a leak. If you get it like once a day or something, its because you are too close to the limit. My suggestion in this case is try to remove some stuff and try again. Also Image memory is outside Heap memory, so look out for both.Jerriejerrilee
If you suspect memory leakage, a quick way to monitor the heap usage is via the adb shell dumpsys meminfo command. If you see heap usage increasing over a few gc cycles (GC_* log row in logcat) you can be pretty sure you have a leak. Then create a heap dump (or several at different times) via adb or DDMS and analyze it via the dominator tree tooling on Eclipse MAT. You should pretty soon find which object has a retaines heap that grows over time. That's your memory leak. I wrote an article with some more details here: macgyverdev.blogspot.com/2011/11/…Nicky
M
98

One of the most common errors that I found developing Android Apps is the “java.lang.OutOfMemoryError: Bitmap Size Exceeds VM Budget” error. I found this error frequently on activities using lots of bitmaps after changing orientation: the Activity is destroyed, created again and the layouts are “inflated” from the XML consuming the VM memory available for bitmaps.

Bitmaps on the previous activity layout are not properly de-allocated by the garbage collector because they have crossed references to their activity. After many experiments I found a quite good solution for this problem.

First, set the “id” attribute on the parent view of your XML layout:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:id="@+id/RootView"
     >
     ...

Then, on the onDestroy() method of your Activity, call the unbindDrawables() method passing a reference to the parent View and then do a System.gc().

    @Override
    protected void onDestroy() {
    super.onDestroy();

    unbindDrawables(findViewById(R.id.RootView));
    System.gc();
    }

    private void unbindDrawables(View view) {
        if (view.getBackground() != null) {
        view.getBackground().setCallback(null);
        }
        if (view instanceof ViewGroup) {
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            unbindDrawables(((ViewGroup) view).getChildAt(i));
            }
        ((ViewGroup) view).removeAllViews();
        }
    }

This unbindDrawables() method explores the view tree recursively and:

  1. Removes callbacks on all the background drawables
  2. Removes children on every viewgroup
Mcrae answered 21/7, 2011 at 15:57 Comment(10)
Except... java.lang.UnsupportedOperationException: removeAllViews() is not supported in AdapterViewEmia
Thanks this helps reduce my heap size significantly since I have a lot of drawables ... @GJTorikian for that just try catch at removeAllViews() most ViewGroup subclasses work just fine.Registrant
Or just change the condition: if (view instanceof ViewGroup && !(view instanceof AdapterView))Bestiary
How can i do this with a GalleryView? Please help me out, your solution worked for backgrounds, but how can i remove the bitmaps from my gallery in OnDestroy?Wiseman
@Adam Varhegyi, you can explicitly call same function for gallery view in onDestroy. Like unbindDrawables(galleryView); hope this works for you...Mcrae
@Mcrae Can we use removeAllViewsInLayout() for AdapterViews ?Derisible
watch out for these two gotcha's #8405975 AND #4486534Ontology
If I am using Fragments then Can I write the above code in onDestroyView() or else in the onDestroy() only? Please some one suggest me.Scrouge
This solution works good. Thanks dude for the great explanation. This may be small thing. But very important. Thanks againWhereabouts
Is the System.gc() really needed here?Peptonize
T
73

It sounds like you have a memory leak. The problem isn't handling many images, it's that your images aren't getting deallocated when your activity is destroyed.

It's difficult to say why this is without looking at your code. However, this article has some tips that might help:

http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html

In particular, using static variables is likely to make things worse, not better. You might need to add code that removes callbacks when your application redraws -- but again, there's not enough information here to say for sure.

Tswana answered 22/12, 2009 at 21:55 Comment(2)
this is true and also having lots of images (and not a memory leak) can cause outOfMemory for multiple reasons like lots of other apps are running, fragmentation of memory, etc. I learned that when working with lots of images if you get the outOfMemory systematically, like doing always the same, then its a leak. If you get it like once a day or something, its because you are too close to the limit. My suggestion in this case is try to remove some stuff and try again. Also Image memory is outside Heap memory, so look out for both.Jerriejerrilee
If you suspect memory leakage, a quick way to monitor the heap usage is via the adb shell dumpsys meminfo command. If you see heap usage increasing over a few gc cycles (GC_* log row in logcat) you can be pretty sure you have a leak. Then create a heap dump (or several at different times) via adb or DDMS and analyze it via the dominator tree tooling on Eclipse MAT. You should pretty soon find which object has a retaines heap that grows over time. That's your memory leak. I wrote an article with some more details here: macgyverdev.blogspot.com/2011/11/…Nicky
B
11

To avoid this problem you can use native method Bitmap.recycle() before null-ing Bitmap object (or setting another value). Example:

public final void setMyBitmap(Bitmap bitmap) {
  if (this.myBitmap != null) {
    this.myBitmap.recycle();
  }
  this.myBitmap = bitmap;
}

And next you can change myBitmap w/o calling System.gc() like:

setMyBitmap(null);    
setMyBitmap(anotherBitmap);
Bield answered 12/6, 2012 at 11:10 Comment(2)
this won't work if trying to add those elements into a listview. do you have any suggestion on that?Bertle
@Bield You're recycling the image before assigning it. Wouldn't that prevent its pixels from rendering?Haga
R
8

I've ran into this exact problem. The heap is pretty small so these images can get out of control rather quickly in regards to memory. One way is to give the garbage collector a hint to collect memory on a bitmap by calling its recycle method.

Also, the onDestroy method is not guaranteed to get called. You may want to move this logic/clean up into the onPause activity. Check out the Activity Lifecycle diagram/table on this page for more info.

Renoir answered 22/12, 2009 at 22:16 Comment(2)
Thanks for the info. I am managing all Drawables and not Bitmaps, so I cant call Recycle. Any other suggestion for drawables? Thanks DanielJerriejerrilee
Thanks for the onPause suggestion. I am using 4 tabs with Bitmap images in them all, so destroying the activity might not happen too soon (if user browses all tabs).Ascus
E
7

This explanation might help: http://code.google.com/p/android/issues/detail?id=8488#c80

"Fast Tips:

1) NEVER call System.gc() yourself. This has been propagated as a fix here, and it doesn't work. Do not do it. If you noticed in my explanation, before getting an OutOfMemoryError, the JVM already runs a garbage collection so there is no reason to do one again (its slowing your program down). Doing one at the end of your activity is just covering up the problem. It may causes the bitmap to be put on the finalizer queue faster, but there is no reason you couldn't have simply called recycle on each bitmap instead.

2) Always call recycle() on bitmaps you don't need anymore. At the very least, in the onDestroy of your activity go through and recycle all the bitmaps you were using. Also, if you want the bitmap instances to be collected from the dalvik heap faster, it doesn't hurt to clear any references to the bitmap.

3) Calling recycle() and then System.gc() still might not remove the bitmap from the Dalvik heap. DO NOT BE CONCERNED about this. recycle() did its job and freed the native memory, it will just take some time to go through the steps I outlined earlier to actually remove the bitmap from the Dalvik heap. This is NOT a big deal because the large chunk of native memory is already free!

4) Always assume there is a bug in the framework last. Dalvik is doing exactly what its supposed to do. It may not be what you expect or what you want, but its how it works. "

Enchiridion answered 11/4, 2012 at 0:52 Comment(0)
C
5

I had the exact same problem. After a few testing I found that this error is appearing for large image scaling. I reduced the image scaling and the problem disappeared.

P.S. At first I tried to reduce the image size without scaling the image down. That did not stop the error.

Cisneros answered 20/12, 2010 at 7:56 Comment(2)
could you post the code on how you did the image scaling, I am facing the same problem and I think this might solve it. Thanks!Scion
@Gix - I believe he means that he had to reduce the size of the images before bringing them into the project resources, thus lowering the memory footprint of the drawables. For this you should use the photo editor of your choice. I like pixlr.comTootsy
H
5

Following points really helped me a lot. There might be other points too, but these are very crucial:

  1. Use application context(instead of activity.this) where ever possible.
  2. Stop and release your threads in onPause() method of activity
  3. Release your views / callbacks in onDestroy() method of activity
Houck answered 7/5, 2012 at 15:12 Comment(0)
P
4

I suggest a convenient way to solve this problem. Just assign the attribute "android:configChanges" value as followed in the Mainfest.xml for your errored activity. like this:

<activity android:name=".main.MainActivity"
              android:label="mainActivity"
              android:configChanges="orientation|keyboardHidden|navigation">
</activity>

the first solution I gave out had really reduced the frequency of OOM error to a low level. But, it did not solve the problem totally. And then I will give out the 2nd solution:

As the OOM detailed, I have used too much runtime memory. So, I reduce the picture size in ~/res/drawable of my project. Such as an overqualified picture which has a resolution of 128X128, could be resized to 64x64 which would also be suitable for my application. And after I did so with a pile of pictures, the OOM error doesn't occur again.

Pumpkinseed answered 16/8, 2011 at 8:31 Comment(1)
This works because you're avoiding the restart of your app. So it is not so much a solution as it is an avoidance. But sometimes that is all that you need. And the default onConfigurationChanged() method in Activity will flip your orientation for you, so if you don't need any UI changes to truly adapt to the different orientations, you may be perfectly happy with the result. Watch out for other things that can restart you, though. You may want to use this: android:configChanges="keyboardHidden|orientation|keyboard|locale|mcc|mnc|touchscreen|screenLayout|fontScale"Saltatory
M
3

I too am frustrated by the outofmemory bug. And yes, I too found that this error pops up a lot when scaling images. At first I tried creating image sizes for all densities, but I found this substantially increased the size of my app. So I'm now just using one image for all densities and scaling my images.

My application would throw an outofmemory error whenever the user went from one activity to another. Setting my drawables to null and calling System.gc() didn't work, neither did recycling my bitmapDrawables with getBitMap().recycle(). Android would continue to throw the outofmemory error with the first approach, and it would throw a canvas error message whenever it tried using a recycled bitmap with the second approach.

I took an even third approach. I set all views to null and the background to black. I do this cleanup in my onStop() method. This is the method that gets called as soon as the activity is no longer visible. If you do it in the onPause() method, users will see a black background. Not ideal. As for doing it in the onDestroy() method, there is no guarantee that it will get called.

To prevent a black screen from occurring if the user presses the back button on the device, I reload the activity in the onRestart() method by calling the startActivity(getIntent()) and then finish() methods.

Note: it's not really necessary to change the background to black.

Moyra answered 23/4, 2012 at 21:43 Comment(0)
V
1

The BitmapFactory.decode* methods, discussed in the Load Large Bitmaps Efficiently lesson, should not be executed on the main UI thread if the source data is read from disk or a network location (or really any source other than memory). The time this data takes to load is unpredictable and depends on a variety of factors (speed of reading from disk or network, size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags your application as non-responsive and the user has the option of closing it (see Designing for Responsiveness for more information).

Valentia answered 7/5, 2012 at 13:6 Comment(0)
P
0

Well I've tried everything I found on the internet and none of them worked. Calling System.gc() only drags down the speed of app. Recycling bitmaps in onDestroy didn't work for me too.

The only thing that works now is to have a static list of all the bitmap so that the bitmaps survive after a restart. And just use the saved bitmaps instead of creating new ones every time the activity if restarted.

In my case the code looks like this:

private static BitmapDrawable currentBGDrawable;

if (new File(uriString).exists()) {
    if (!uriString.equals(currentBGUri)) {
        freeBackground();
        bg = BitmapFactory.decodeFile(uriString);

        currentBGUri = uriString;
        bgDrawable = new BitmapDrawable(bg);
        currentBGDrawable = bgDrawable;
    } else {
        bgDrawable = currentBGDrawable;
    }
}
Peden answered 18/4, 2012 at 6:40 Comment(1)
wouldn't this leak your context? (activity data)Bertle
C
0

I had the same problem just with switching the background images with reasonable sizes. I got better results with setting the ImageView to null before putting in a new picture.

ImageView ivBg = (ImageView) findViewById(R.id.main_backgroundImage);
ivBg.setImageDrawable(null);
ivBg.setImageDrawable(getResources().getDrawable(R.drawable.new_picture));
Chaussure answered 20/5, 2013 at 23:22 Comment(0)
S
0

FWIW, here's a lightweight bitmap-cache I coded and have used for a few months. It's not all-the-bells-and-whistles, so read the code before you use it.

/**
 * Lightweight cache for Bitmap objects. 
 * 
 * There is no thread-safety built into this class. 
 * 
 * Note: you may wish to create bitmaps using the application-context, rather than the activity-context. 
 * I believe the activity-context has a reference to the Activity object. 
 * So for as long as the bitmap exists, it will have an indirect link to the activity, 
 * and prevent the garbaage collector from disposing the activity object, leading to memory leaks. 
 */
public class BitmapCache { 

    private Hashtable<String,ArrayList<Bitmap>> hashtable = new Hashtable<String, ArrayList<Bitmap>>();  

    private StringBuilder sb = new StringBuilder(); 

    public BitmapCache() { 
    } 

    /**
     * A Bitmap with the given width and height will be returned. 
     * It is removed from the cache. 
     * 
     * An attempt is made to return the correct config, but for unusual configs (as at 30may13) this might not happen.  
     * 
     * Note that thread-safety is the caller's responsibility. 
     */
    public Bitmap get(int width, int height, Bitmap.Config config) { 
        String key = getKey(width, height, config); 
        ArrayList<Bitmap> list = getList(key); 
        int listSize = list.size();
        if (listSize>0) { 
            return list.remove(listSize-1); 
        } else { 
            try { 
                return Bitmap.createBitmap(width, height, config);
            } catch (RuntimeException e) { 
                // TODO: Test appendHockeyApp() works. 
                App.appendHockeyApp("BitmapCache has "+hashtable.size()+":"+listSize+" request "+width+"x"+height); 
                throw e ; 
            }
        }
    }

    /**
     * Puts a Bitmap object into the cache. 
     * 
     * Note that thread-safety is the caller's responsibility. 
     */
    public void put(Bitmap bitmap) { 
        if (bitmap==null) return ; 
        String key = getKey(bitmap); 
        ArrayList<Bitmap> list = getList(key); 
        list.add(bitmap); 
    }

    private ArrayList<Bitmap> getList(String key) {
        ArrayList<Bitmap> list = hashtable.get(key);
        if (list==null) { 
            list = new ArrayList<Bitmap>(); 
            hashtable.put(key, list); 
        }
        return list;
    } 

    private String getKey(Bitmap bitmap) {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        Config config = bitmap.getConfig();
        return getKey(width, height, config);
    }

    private String getKey(int width, int height, Config config) {
        sb.setLength(0); 
        sb.append(width); 
        sb.append("x"); 
        sb.append(height); 
        sb.append(" "); 
        switch (config) {
        case ALPHA_8:
            sb.append("ALPHA_8"); 
            break;
        case ARGB_4444:
            sb.append("ARGB_4444"); 
            break;
        case ARGB_8888:
            sb.append("ARGB_8888"); 
            break;
        case RGB_565:
            sb.append("RGB_565"); 
            break;
        default:
            sb.append("unknown"); 
            break; 
        }
        return sb.toString();
    }

}
Shirt answered 10/11, 2013 at 4:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.