Everything you need to know about Memory Leaks in Android.

Ali Asadi
ProAndroidDev
Published in
17 min readJun 30, 2019

--

One of the core benefits of Java, or to be more accurate, of the JVM (Java Virtual Machine), is the garbage collector (GC). We can create new objects without worrying about freeing them up from memory. The garbage collector will take care of allocating and freeing up the memory for us.

We won’t have to worry about freeing allocated objects because the garbage collector will allocate and free memory for us? Definitely not! We should be concerned without a solid understanding of how the garbage collector works, we can prevent it from freeing memory, resulting in memory leaks that can lead to out-of-memory exceptions and lags in the app.

What is Memory Leak?

Failure to release unused objects from the memory

Failure to release unused objects from the memory means that there are unused objects in the application that the GC cannot clear from memory.

When the garbage collector (GC) fails to remove unused objects from memory. The memory unit that holds the unused objects will be occupied until the end of the application or method (depends on the scenario).

Memory Leaks: Temporary vs Permanent

Leaks can be divided into two categories: those that occupy the memory unit until the application terminates (permanent) and those that occupy the memory unit until the method terminates (temporary).

The first category, application terminations (permanent), is self-evident: when an app is terminated, the GC releases all of the memory that the app has used.

The second category, method terminations (temporary), requires a little more explanation; for a better understanding, let’s use an example.

Let’s say we have method X. Method X is performing a task in the background that will take five minutes to complete and we’ve given it an activity context to retrieve some string. We decided to close the activity after one minute, but the method is still running and it needs four more minutes to complete the task; during this time we are holding the activity object, which is no longer in use, and the GC will be unable to free it from the memory; once the method completes the task, the GC will run again and will be able to clear and reclaim them from memory.

Wait a minute!!

That’s all I’d like you to know about memory leaks and garbage collector for now; we’ll return to it later and do a deeper dive with code and visualization, but first let’s start with the basics 🙌🏼.

What is RAM (Memory)?

RAM, or Random access memory, is the memory in android devices or computers that is used to store current running applications and their data.

I’m going to explain two main characters in the RAM, the first is the Heap, and the second is the Stack. Let’s move on to the fun part 🤩🍻.

Heap & Stack

I’m not going to make it too long. Let’s get right to the point, a short description, the Stack is used for static memory allocation while the Heap is used for dynamic memory allocation. Just keep in mind that both the Heap and the Stack are stored in the RAM.

More about the Heap Memory

Java heap memory is used by the virtual machine to allocate objects. Whenever you create an object, it’s always created in the heap. Virtual machines, like JVM or DVM, perform regular garbage collection (GC), making the heap memory of all objects that are not referenced anymore available for future allocations.

To provide a smooth user experience, Android sets a hard limit on the heap size for each running application. The heap size limit varies among devices and is based on how much RAM a device has. If your app hits this heap limit and tries to allocate more memory, it will receive an OutOfMemoryError and will terminate.

Have you ever wondered what the heap size for your application is?

Let’s discover this together. In android, we have the Dalvik VM (DVM). The DVM is a unique Java Virtual Machine optimized for mobile devices. It optimizes the virtual machine for memory, battery life, and performance, and it is responsible for distributing the amount of memory for each application.

Let’s talk about two lines in the DVM:

  1. dalvik.vm.heapgrowthlimit: This line is based upon how Dalvik will start in the heap size of your application. It is the default heap size for each application. The maximum that your app can reach!
  2. dalvik.vm.heapsize: This line represents the maximum heap size for a larger heap. You can achieve that by asking android for a larger heap in your application manifest (android:largeHeap=”true”).

Don’t use a larger heap in your app. Do that ONLY if you know exactly the side effect of this step. Here I will give you enough information to continue researching the topic.

Here is a table showing what heap size you got based on your device RAM:

+==========================+=========+=========+===================+
| DVM | 1GB RAM | 2GB RAM | 3GB RAM OR HIGHER |
+==========================+=========+=========+===================+
| DEFAULT(heapgrowthlimit) | 64m | 128m | 256m |
+--------------------------+---------+---------+-------------------+
| LARGE(heapsize) | 128m | 256m | 512m |
+--------------------------+---------+---------+-------------------+

Remember the more ram you have the higher the heap size will be. kept in mind not all devices with higher ram go above 512m do your research on your device if your device has more than 3GB to see if your heap size is bigger than 512m.

How can you check the app heap size for your device?

Using the ActivityManager. You can check the maximum heap size at runtime by using the methods getMemoryClass() or getLargeMemoryClass() (when a large heap is enabled).

  • getMemoryClass(): Return the default maximum heap size.
  • getLargeMemoryClass(): Return the maximum available heap size after enabling the large heap flag in the manifest.
ActivityManager am = getSystemService(ACTIVITY_SERVICE);
Log.d("XXX", "dalvik.vm.heapgrowthlimit: " + am.getMemoryClass());
Log.d("XXX", "dalvik.vm.heapsize: " + am.getLargeMemoryClass());

How does it work in the real world?

We’ll use this sample app to show how data is stored in the heap and stack.

application

The image below represents the heap and stack of the app, and it shows where each object points and is stored when the app is run.

We’ll go over each line of the app’s execution and explain when objects are allocated, stored, and released from the heap or stack.

Line 1 — The JVM creates a stack memory block for the main method.

Line 2 — A primitive local variable is created in this line. The variable will be created and stored in the stack memory of the main method.

Line 3 — In this line, we create a new object. The object is stored on the heap, and the object heap memory address is stored on the stack.

Note: The object heap memory address is stored in the main method’s stack.

Line 4 — The same as line 3.

Line 5 — The JVM creates a stack memory block for the foo method.

Line 6 —We create a new object in the stack memory of the foo method that stores the heap memory address of the object we passed in line 5.

Note: Java always passes parameter variables by value. Object variables in Java always point to the real object in the memory heap.

Line 7 — We create a new object in the stack of the foo method that stores the heap memory address of the string pool.

Line 8 — When it got to the last line, the foo method terminated. And the objects in the foo method’s stack block will be released.

Line 9— The same as line 8. When it got to the last line, the main method terminated. And the objects in the main method’s stack block will be released.

What about releasing memory from the heap? We’ll be there shortly. Grab a cup of coffee ☕️ and keep going.

What happens when methods are terminated?

Each method has its own scope. When the method is finished, the objects are automatically released and reclaimed from the stack.

figure 1

When the foo method is terminated, the stack block of the foo method is automatically released and reclaimed, as shown in Figure 1.

figure 2

The same as in Figure 2. When the main method is terminated, the stack block of the main method is automatically released and reclaimed

Conclusion

It’s now clear that the objects in the stack are only there for a limited time. Once the method is complete, the objects will be released and reclaimed.

The stack is a LIFO (Last-In-First-Out) data structure. You can view it as a box. By using this structure, the program can easily manage all its operations by using two simple operations: push and pop.

Every time you need to save something like a variable or method it pushes and moves the stack pointer up. Every time you exit from a method, it pops everything from the stack pointer until the return to the address of the previous method. In our example returning from the foo method to the main method.

What about the Heap?

The heap is different from the stack. For releasing and reclaiming the objects from heap memory, we need help.

For that, Java, or to be more accurate, the JVM has made a superhero for helping us. We called it the Garbage Collector. He is going to do the hard work for us. And caring about detecting unused objects, release them, and reclaim more space in the memory.

How does the garbage collector work?

Simple. The garbage collector is looking for unused or unreachable objects. When there is an object in the heap that does not have any reference pointed to it, the garbage collector will take care of releasing it from memory and reclaim more space.

GC

GC roots are objects referenced by the JVM. They are the initial objects of the tree. Every object in the tree has one or more root objects. As long as the application or the GC roots can reach those roots or those objects, the whole tree is reachable. Once they become unreachable from the application or the GC roots, they will be considered as unused objects or unreachable objects.

What is the state of the memory before and after the GC operation?

Before GC

This is the current state of the application’s memory. The heap is full of unused objects, and the stack is empty.

After GC

The results of running the GC are shown above. All unused objects in the heap will be released and cleared by the GC.

Man! What about the memory leak we’ve been expecting? There were no leaks in the sample app. I just wanted to show how data is stored and released from the heap and stack. In the following examples, we’ll show some samples with leaks. This was just a warm-up for the next section.

When and how do memory leaks happen?

A memory leak happens when the stack still refers to unused objects in the heap.

The image below provides a sample visual representation of the concept for a better understanding.

In the visual representation, we see that when we have objects referenced from the stack but not in use anymore. The garbage collector will never release or freeing them up from the memory cause it shows that those objects are in use while they are not.

How we can cause a leak?

A memory leak can easily occur in Android when AsyncTasks, Handlers, Singletons, Threads, and other components are used incorrectly.

I’ll use threads, singletons, and listeners to demonstrate how we can cause leaks and how to avoid and fix them.

Check out my Github repository. I’ve got some code examples for you.

Examples:

1. How can an incorrect use of a thread cause a leak?

In this example, we’ll look at an activity that runs a thread in the background for 20 seconds when it starts.

Inner classes, as is well known, have an implicit reference to the outer class object that instantiated them.

This is how the activity looks behind the scenes.

Important: The DownloadTask holds a reference to the ThreadActivity.

So, what happens after the task or thread is started?

There are two possibilities. An unexpected flow that causes a memory leak, and a regular flow that works as expected with no errors.

1. Regular Flow

The application’s heap and stack are depicted in the diagram.

The user launched the application, opened the ThreadActivity.

Task Running

The task is being performed in the background. The user waited 20 seconds for the download task to complete.

When the task is completed, the run method’s stack block will be released and reclaimed. The DownloadTask reference has been removed.

When the GC runs, the DownloadTask object is reclaimed from the heap and released.

The activity is now closed by the user. When the activity is closed the main method’s stack block will be released and reclaimed, and when the GC runs, the ThreadActivity object is reclaimed and released from the heap.

Perfect!

2. Unexpected Flow

The user starts the application, opens the ThreadActivity, and the download task starts running in the background. As we know, It takes 20 seconds for the task to complete the download process.

If the user decided after being 10 seconds in the thread activity to exit the activity or go back to the previous screen or even rotate the device from portrait to landscape. We are in trouble.

When we exit the activity, the stack releases all the objects. But since the run method still there, we need to wait for it. The run method is a part of the DownloadTask or, to be more accurate, the run method is a part of the thread class, but since the DownlaodTask object extends the thread class, it’s a part of it as well. We have seen before that the DownloadTask is an inner class, and the inner classes are holding a reference for their enclosing class, which means that the DownloadTask object is holding a reference of the activity. In that case, the thread is holding the activity reference, which means that the activity still alive, and the GC cannot clear it from the heap memory. Although, we exist the activity, and it’s not in use anymore.

Anddd… BOOM💣 , we have a memory leak ☠️️, the task still running in the background, and the activity still alive.

REMINDER:

At the beginning of the post, I told you that there are two kinds of leaks, leaks that occupy the memory unit until the end of the application and leaks that occupy the memory unit until the end of the method.

This is the second type, a leak that occupies the memory unit until the end of the method.

That means the memory unit of the ThreadActivity occupies until the end of the task. The task needs 20 seconds to finish. The user exit the activity after 10 seconds that keeps another 10-second for the task to finish. In this situation, the memory unit will be occupied for the next 10-seconds until the end of the task. After 10 seconds, and when the task is completed. The stack will clear the block of the run method, and in the next GC cycle, the GC will clear the references from the heap.

Heap Dump

A heap dump using the profiler tool that android studio provides, we dump the heap of the previous application. What we will see in the heap dump is a flow of a user that rotates his device 5 times while using the application. Each time he rotates the device he causes a leak. So we have five leaks, five tasks running in the background, and five leaking activities.

2. How can we cause a leak using a singleton?

Let’s take an example of a singleton who needs context to get a resource or a shared preference instance.

Is there something wrong with the singleton class above? The answer is NO. The way you use the singleton is, but the singleton class itself is fine.

When could the memory leak happen?

The memory leak could happen when we initialize the singleton from the activity and pass a long-lived activity-context reference to the singleton constructor when we initialized it.

In that example, when we pass an activity-context, the singleton class will hold the activity (LoginActivity) instance until the end of the application.

Profiler

What is PermGen?

PermGen is an abbreviation for Permanent Generation and it’s a special heap space that is separate from the main Java heap. static methods and static variables are stored in the PermGen section of the heap. only primitives and references are stored in the PermGen the value is stored in another part of the heap (young/old generation).

#Oracle

How to avoid singleton leak?

  1. By using application-context instead of activity-context.

Do not keep long-lived references to a context-activity

2. In a situation in which the activity-context is required. Remove the context reference when it is no longer needed. For example, when the activity is destroyed.

3. By using the application-context when creating the singleton object. You’ll always use the application context regardless of which context the getInstance() method was initialized with.

3. Using Toast

In Android development, using the activity-context to show a Toast is a common mistake that can easily lead to a leak.

Toast.makeText(applicationContext, text, duration)

To avoid these leaks, bypass the application-context. The application-context is also used in the Toast documentation examples.

3. How can we cause a leak using listeners?

The listener or observer pattern is the most common strategy for creating callbacks within Android development. There are many situations where you register a listener in your activity or fragment for a singleton object or an x-manager but forget to unregister it. This situation can easily lead to a memory leak. Since the activity instance is getting hold by singleton, or x-manager you have registered to it.

In this code example, we registered the activity as a listener for location updates when it started, and we unregistered the listener when it stopped.

If we forget to unregister the listener in onStop(), the LocationManager will hold the activity reference, preventing the GC from freeing the memory. This is a great example of how listeners can lead to memory leaks.

How to avoid these kinds of leaks?

Make sure to unregister all your listeners in onDestroy/onStop methods.

Why should we care about memory leaks?

Memory leaks can lead to lags in the app, ANR errors, and OutOfMemory exceptions. Which ultimately leads to the uninstallation of your app by users.

1. Lags in the app.

A large heap with memory leaks can result in an excessive number of GC operations, which can cause app lags and frame drops.

Our goal is to deliver a high-quality product that renders 60 frames per second to avoid lags and ensure a smooth experience.

How does it work?

FlipBook is presenting 12 frames per second.

The transaction between the frames is not smooth, as you can see.

To achieve a smooth motion, we aim to present 60 frames per second (fps). Anything higher than 60 frames per second won’t make a difference, so we’ll aim for 60 frames per second at the very least.

Second / 60 frams =16.666 millisecond.

To achieve 60 frames per second, we need to render a frame every 16.666 milliseconds. If we have a large heap, we will need to perform many GC operations in order to free up space in the app. These operations may take place while the frame is being rendered, causing the frame to drop and causing lags.

You can track the GC operations logs by filtering for “GC” in the android studio logcat search bar.

I/com.package: Background young concurrent copying GC freed 870325(19MB) AllocSpace objects, 54(3412KB) LOS objects, 47% free, 22MB/43MB, paused 437us total 161.921msI/com.package: Background young concurrent copying GC freed 454942(18MB) AllocSpace objects, 61(2300KB) LOS objects, 38% free, 26MB/43MB, paused 594us total 177.824ms

2. Application Not Responding (ANR)

When the UI thread of an Android app is blocked for too long, an “Application Not Responding” (ANR) error is triggered.

Image result for anr android

3. Crash — OutOfMemoryError exception

One common indication of a memory leak is the OutOfMemoryError exception. 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.

Tools that can help you identify leaks

  • LeakCanary is a great tool for detecting memory leaks in your app.
  • Profiler View the Java heap and memory allocations with Memory Profiler

That is all. Thanks for reading! I hope you enjoyed this article and found it useful, if so please click the 👏 button and share to help others find it.

Happy coding!

--

--