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
A big warning sign to lookout for is the ever increasing memory usage in your app, as depicted by Androids Memory Monitor:
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.
- Open Android Studio, open the Android Monitor tab.
- Run your application, and select it from the list of available apps.
- 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 🙃)
- The trick here, is to catch the app before the OutOfMemoryException.
- Click on the memory tab in Android Monitor.
- You should see a nice graph starting to draw. When you are ready, click “Initiate GC”. (The little red garbage truck)
- 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.
- 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.
- 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
- Once converted, open the file in MAT. Select “Leak Suspects Report” and click finish.
- 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.
- 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.
- 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”.
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.
- From the below information, it is clear that there is a DisplayListener that gets registered but never unregistered.
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?