Categories
android

Fixing Memory Leaks in Android – OutOfMemoryError

Memory leaks in Android are quite easy to create. The unsuspecting developer might be making a few memory leaks every days without realising. You probably haven’t noticed them yet or even know that they exist. Until you see an exception like this….

java.lang.OutOfMemoryError: Failed to allocate a 4308492 byte allocation with 467872 free bytes and 456KB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444)
at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:988)
at android.content.res.Resources.loadDrawableForCookie(Resources.java:2580)
at android.content.res.Resources.loadDrawable(Resources.java:2487)
at android.content.res.Resources.getDrawable(Resources.java:814)
at android.content.res.Resources.getDrawable(Resources.java:767)
at com.nostra13.universalimageloader.core.DisplayImageOptions.getImageOnLoading(DisplayImageOptions.java:134)

What? What does that even mean? Does it mean my bitmap is too large for the Android System?

Unfortunately, seeing this kind of Stack trace may be a bit deceiving. When you receive an OutOfMemoryError, it generally means, 9 times out of 10 that you have a memory leak. My first impressions of this stack trace left me confused and thinking that my bitmap was too large… Boy was I wrong.

What is a memory leak?

A failure in a program to release discarded memory, causing impaired performance or failure.

How is a memory leak caused in Android?

Memory leaks in Android are actually quite easy to make, which is probably part of the problem. The biggest issue is the Android Context object.

Every app has a global application context (getApplicationContext()). Every activity is a subclass of Context , which stores information related to the current activity. More often than not, your memory leak will be associated with a leaked activity.

The unassuming developer would pass this context object around to the threads that need it. Make some static TextViews that hold reference to the activity. You know, things that make it work? sarcasm intended, do not try those things at home

The opposite of how things will work.

A big warning sign to lookout for is the ever increasing memory usage in your app, as depicted by Androids Memory Monitor:

Memory Monitor showcasing a memory leak in action
Android Memory Monitor running with an app that has a memory leak
Android Memory Monitor After fixing memory leak
Android Memory Monitor after fixing the memory leak

As you can see, the first graph, the app is never able to really regain some of the memory used. It used up to about 300MB at one point before the OutOfMemoryError occurred. The second graph shows that the app is able to garbage collect, regain some memory and stays pretty consistent in its memory usage.

How do I avoid memory leaks?

  • Avoid passing Context objects further that your activity or fragment
  • NEVER EVER EVER make/store a Context or View in a static variable. This is the first sign of a memory leak.
    private static TextView textView; //DO NOT DO THIS
    private static Context context; //DO NOT DO THIS
  • Always unregister listeners in your onPause()/ onDestroy() methods. This includes Android listeners, to things such as Location services or display manager services and your own custom listeners.
  • Don’t store strong references to activities in your AsyncTasks or background threads. Your activity may get closed, but your AsyncTask will continue execution and hold onto that reference of your activity.
  • Use Context-application (getApplicationContext()) instead of Context from an activity if you can.
  • Try not to use non-static inner classes if you can avoid it. Storing reference to something like an Activity or View inside this can lead to memory leaks. Use WeakReference if you need to store reference to them.

How do I fix it?

Fixing memory leaks takes a bit of practise and lots of trial and error. Memory leaks can be very difficult to track down. Luckily there are a few tools that can help you identify a possible leak.

You can watch this video which shows how to obtain the HPROF file to use for analysis, or you can read the tutorial below describing how to do it.

  1. Open Android Studio, open the Android Monitor tab.
  2. Run your application, and select it from the list of available apps.
  3. Do some actions in your app that lead you to a similar point. For me, it was selecting a new video, and playing it about 50 times. (I wrote a test for this 🙃)
  4. The trick here, is to catch the app before the OutOfMemoryException.
  5. Click on the memory tab in Android Monitor.Screen Shot 2016-01-26 at 11.12.00 PM
  6. You should see a nice graph starting to draw. When you are ready, click “Initiate GC”. (The little red garbage truck)
  7. Click “Dump Java Heap” and wait a couple of seconds. (The icon below the truck with the green arrow). This will generate a .hprof file that you can use to analyse the memory usage.
  8. Unfortunately the Android Studio Hprof file viewer doesn’t have all the tooling that Eclipse Memory Analyzer has. So you will need to install MAT.
  9. Run the following command to convert a .hprof file from Android into one MAT will understand. (The hprof-conv tool is located in the platform tools folder of the sdk)
    ./hprof-conv path/file.hprof exitPath/heap-converted.hprof
    
  10. Once converted, open the file in MAT. Select “Leak Suspects Report” and click finish.

    Opening Eclipse Memory Analyzer - Choose Leak Suspects Report
    Opening Eclipse Memory Analyzer – Choose Leak Suspects Report
  11. Click on the 3 blue bars at the top “Create a histogram from an arbitrary set of objects”. You will then see a list of objects that are taking up memory.

    Eclipse Memory Analyzer - Histogram
    Eclipse Memory Analyzer – Histogram
  12. Seeing this list of objects might be a bit confusing. You can filter the objects by class name, so I suggest typing your package name in the class name filter.

    Filter objects by class name in Eclipse Memory Analyzer
    Filter objects by class name in Eclipse Memory Analyzer
  13. Now we can see that there are 9 instances of VideoDetailActivity  which is obviously not right as we should only have one. To find out what is holding reference to the VideoDetailActivity , right click on the item and select “Merge Paths to Shortest GC Root”, and then click “exclude all phantom/weak/soft etc. references”.
    Merge Shortest Path to GC Roots - Eclipse Memory Analyzer
    Merge Shortest Path to GC Roots – Eclipse Memory Analyzer

    The thread that is holding reference will be shown. You will then be able to drill down into what exactly is holding reference to the activity.

  14. From the below information, it is clear that there is a DisplayListener that gets registered but never unregistered.
    Eclipse Memory Analyzer - Memory leak Identification
    Eclipse Memory Analyzer – Memory leak Identification

    So this memory leak is solved by calling unregister on the display listener that was registered.

    DisplayManager displayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
    displayManager.unregisterDisplayListener(listener);

    Not all memory leaks are this easy to find, some are a lot more difficult, but hopefully this guide gets you started with trying to figure out where the problem lies and hopefully avoid potential memory leaks. There are lots of other tools that can be useful in finding memory leaks, check them out here.

How do you find and fix memory leaks? How long have you spent digging around trying to find the cause?

Links:

9 replies on “Fixing Memory Leaks in Android – OutOfMemoryError”

I’m glad you provided us suggestions to avoid memory leaks. I sometimes make these mistakes, and it is hard to rely on Android Studio monitoring tools to find memory leaks. I am just curious – why you advocate getApplicationContext() vs activity context? I mean, it seems unorthodox and I haven’t seen this approach advocated in any samples.

Hi Igor,
Using getApplicationContext() means a less memory leaks. If you had to hold reference to getApplicationContext() it probably isn’t as dangerous as getContext(), using getContext() is fine for the most part, as long as you know what you are doing with not storing it in background threads.

It is great, only part about it that I don’t like is the number of methods that it brings with it! 🙂 Reached a dex limit after including it. 🙁

@Rebecca Have you tried the 1.4 beta? Footprint should hopefully be smaller. LeakCanary has no impact on release builds, but yep you could reach the dex limit in dev and that’d be annoying. There are ideas to reduce it even more in dev by installing a separate app for the automated analysis.

Comments are closed.