Out Of Memory Exception.

Aman Shekhar
8 min readOct 8, 2021

In Java, all objects are stored in the heap. Every Android developer must have encountered the OutOfMemoryError, also known as OOME. Usually, this error is thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector.

What exactly is a Memory Leak?

When you run an application on an Android device, the Android system allocates memory to it in order for it to function. All variables, functions, and activity creation, for example, takes happen alone in that memory.

Example: If the Android System assigns 200MB to your program, for example, your application can only use 200MB at a time. If the amount of space allotted to the program is reduced over time, or if very little space is available, the Garbage Collector (GC) will release the memory held by variables and activities that are no longer in use. As a result, the application will get some space once more.

To make sure each app in Android has enough memory, Android system needs to manage memory allocation efficiently. Android runtime triggers Garbage Collection (GC) when memory runs short. The purpose of GC is to reclaim memory by cleaning up objects that are no longer useful. It achieves it in three steps.

  • Traverses all object references in memory from GC roots and marks active objects which have references from GC roots.
  • All objects which are not marked (garbages) are wiped from memory.
  • Rearrange live objects

Whenever unused objects are referenced somehow from reachable objects, GC would mark unused objects as a useful object and therefore would not be able to remove them. This is called a memory leak.

Is Memory Leak Bad?

Let’s see the disadvantages of the memory leak:

1. If objects are staying longer than they should, they will occupy valuable resources. Less usable memory would be available when memory leak happens. As a result, Android system will trigger more frequent GC events. GC events are stop-the-world events. It means when GC happens, the rendering of UI and processing of events will stop. Android has a 16ms drawing window. When GC takes long than that, Android starts loosing frames. unused objects are referenced somehow from reachable objects, GC would mark unused objects as a useful object and therefore would not be able to remove them. This is called a memory leak.

2. When your app has memory leaks, it cannot claim memory from unused objects. As a result, it will ask Android system for more memory. But there is a limit. The system will eventually refuse to allocate more memory for your app. When this happens, app user will get an out-of-memory crash.

Below are the few ways which can cause a memory leak:

  • Leak activity to a static reference.
  • Leak activity to a worker thread.
  • Leak thread itself.
  • We should avoid passing Context objects further that our activity or avoid passing Context objects further that your activity or fragment
  • Never store a Context or View in a static variable. This is the first sign of a memory leak.
  • 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.
  • Use Context-application (getApplicationContext()) instead of Context from an activity if you can. fragment
  • NEVER EVER EVER make/store a Context or View in a static variable. This is the first sign of a memory leak.
  • 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.
  • Use Context-application (getApplicationContext()) instead of Context from an activity if you can.

Now let’s find out a way to identify a leak?

There are several ways with the help of which you can identify a leak in your application, few are already inbuilt with the Android Studio. Let’s have a look:

  1. There is one tool called Leak Canary, it is a very wonderful tool to find out all the leaks in your app.
  2. Android Studio itself has a handy tool for detecting memory leaks. Follow the below process:
    a. Run the app and play around with it.
    b. In Android Studio -> Android Monitor window -> Memory section, click on Initiate GC button. Then click on Dump Java Heap button.
    c. When Dump Java Heap button is pressed, Android Studio will open the dumped .hprof file. In the hprof file viewer, there are a couple of ways you can check the memory leak. You can use the Analyzer Tasks tool on the top right corner to detect leaked activities automatically. Or you can switch the view mode to Package Tree View from top left corner, find the activity which should be destroyed. Check the Total Count of the activity object. If there are 1 or more instances, it means there is a leak.
  3. Use StrictMode: StrictMode is a developer tool which detects things you might be doing by accident and brings them to your attention so you can fix them. StrictMode is most commonly used to catch accidental disk or network access on the application’s main thread, where UI operations are received and animations take place. Keeping disk and network operations off the main thread makes for much smoother, more responsive applications.
public void onCreate() {
if (DEVELOPER_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate();
}

Till now we covered, what is Memory Leak, what causes and how to identify it. Now let us deep dive into code, on how to fix it.

We should be following some of the best practices:

  • The inner class should be static, because static classes don’t require an implicit reference to the outer class.
  • Un-initialise the value when not needed.
  • Unregister the receiver in a onStop() or onDestroy()
  • Correct Use of getContext() and getApplicationContext()

Code Fix:

  1. Enable android:largeHeap="true", although this should be the last resort. We should totallly avoid this.
  2. Use ActivityManager class to identify the allocated memory and the memory allocated after enabling android:largeHeap="true".
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
// Below code return the allocated memory for your app.am.getMemoryClass();
// Below code return the allocated memory when an application is
// running with a
android:largeHeap="true".
am.getLargeMemoryClass();
PS. The memory is allocated depending on device RAM size.

3. Explore ActivityManager class in deep:

CODE USAGE:

private ActivityManager.MemoryInfo getAvailableMemory() {
ActivityManager activityManager =
(ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
return memoryInfo;
}

Now in onStop() method, check whether your app is running low on memory or not, if Yes, then release the memory. But imagine a situation if the leaks are coming from the 3rd party library.

/**
* This method will be called when the User press back, or even the home button.
* So, in this method we will be checking whether your app's memory is running low or not.
* If it is running low, then we will be killing the process.
* And on re-launch it will start from the fresh.
*/
@Override
protected void onStop() {
ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();
if (memoryInfo.lowMemory) {
int processId = Process.myPid();
Process.killProcess(processId);
}
super.onStop();
}

Make use of onTrimMemory():

public abstract void onTrimMemory (int level);

Called when the operating system has determined that it is a good time for a process to trim unneeded memory from its process. This will happen for example when it goes in the background and there is not enough memory to keep as many background processes running as desired. You should never compare to exact values of the level, since new intermediate values may be added — you will typically want to compare if the value is greater or equal to a level you are interested in.

CODE USAGE:

/*
* Called when the operating system has determined
* that it is a good time for a process to trim unneeded memory from its process.
* This will happen for example when it goes in the background
* and there is not enough memory to keep as many background
* processes running as desired.
*
*/

@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);

switch (level) {
// When your app is running, then below three callbacks will be received.

case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
/*
* The device is running extremely low on memory.
* Your app is not yet considered a killable process,
* but the system will begin killing background
* processes if apps do not release resources,
* so you should release non-critical
* resources now to prevent performance degradation.
*
*/

case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
/*
* The device is running much lower on memory.
* Your app is running and not killable,
* but please release unused resources to
* improve system performance
* (which directly impacts your app's performance).
*/

case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
/*
* The device is beginning to run low on memory.
* Your app is running and not killable.
*/


case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
/*
* Your app's UI is no longer visible,
* so this is a good time to release large
* resources that are used only by your UI.
*/
Runtime.getRuntime().gc();
break;

// This invokes the JVM to perform Garbage Collection,
// if your app is eligible then it will perform GC.
// When your app's visibility changes,
// then below callback will be received.

// When your app's process resides in the background LRU list:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
/*
* The system is running low on memory and your process
* is one of the first to be killed if
* the system does not recover memory now.
* You should release absolutely everything
* that's not critical to resuming your app state.
*/

case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
/*
* The system is running low on memory and
* your process is near the beginning of the LRU list.
* Although your app process is not at a high risk of being killed,
* the system may already be killing processes in the LRU list,
* so you should release resources that are easy to recover
* so your process will remain in the list and
* resume quickly when the user returns to your app.
*/

case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
/*
* The system is running low on memory and your
* process is near the middle of the LRU list.
* If the system becomes further constrained for memory,
* there's a chance your process will be killed.
*/

int processId = Process.myPid();
Process.killProcess(processId);
break;

default:
throw new IllegalStateException("Unexpected value: " + level);
}
}

Now the question is, how will we replicate these scenario. adb commands comes to rescue.

/*
* Run the below code to replicate the behaviour:
* GoTo platform-tools, then use this command
* For TRIM_MEMORY_COMPLETE :
* adb shell am send-trim-memory com.freecharge.business COMPLETE

* For TRIM_MEMORY_RUNNING_LOW :
* adb shell am send-trim-memory com.freecharge.business RUNNING_LOW

* For TRIM_MEMORY_RUNNING_CRITICAL :
* adb shell am send-trim-memory com.freecharge.business RUNNING_CRITICAL

* For TRIM_MEMORY_RUNNING_MODERATE :
* adb shell am send-trim-memory com.freecharge.business RUNNING_MODERATE

* For TRIM_MEMORY_BACKGROUND :
* adb shell am send-trim-memory com.freecharge.business BACKGROUND
* (This should be called when app is not visible).

* For TRIM_MEMORY_COMPLETE :
* adb shell am send-trim-memory com.freecharge.business COMPLETE

* PS: Don't set background level if your app is in foreground.
* Otherwise the following exception will be thrown, as shown in the video.
* java.lang.IllegalArgumentException:
* Unable to set a background trim level on a foreground process
*/

Please do try these, and try to stay away from Memory Leak.

Happy Coding!!!

--

--

Aman Shekhar

Mobile App Developer by profession, a Chess Player by heart, and an Aspiring Author by ambition.