- Published on
Android App Memory: Must-Know Knowledge
- Authors
- Name
- Dan Tech
- @dan_0xff
Memory is a very important topic in programming, especially in Android. Let's explore it in more detail!
App Memory in Android
Android's RAM memory is divided into two main parts: Stack and Heap.
Stack
The stack is where temporary variables are stored when the program is running. These variables will be completely released when the program exits the functions, loops, or conditional statements.
fun foo() {
var i = 10 // Stack memory
var o = Object() // o is on Stack, Object is on Heap
}
The size of the Stack is very small (a few MB), so its usage is very limited. When you run out of Stack memory, the program will encounter a Stack Overflow error.
Most program data will be allocated on the Heap.
Heap
The Heap is a partition used to store Objects in your Android program. Simply put, the Heap is where data created after the word "new" (for Java) or the Objects that are created (for Kotlin) are stored.
fun f() {
val point = Point(2, 5) // Point(2,5) is stored on Heap
}
Although the Heap is a much larger partition than the Stack, Android still limits its capacity. When memory usage reaches the extreme threshold of the Heap, the program will throw an OutOfMemoryError.
Garbage Collector
Garbage Collector (GC) is a feature in modern languages (Java, Kotlin, Go..). GC has the right to reclaim memory areas on the Heap when they are no longer referenced by other variables.
fun sample() {
var object = Object("a") // object references to Object("a")
var object = null // object reference to null Object("a") now is a free memory,
// Object("a") could be collected by GC in some time in the future
}
Memory Leaks
A Memory Leak is the situation where a variable or a portion of memory on the Heap, although no longer used for the purpose of running the program, has not yet been reclaimed by the GC. This wastes machine resources and can easily lead to an OutOfMemoryError.
The reason for this is generally that the variables are no longer used by the program but cannot be reclaimed by the GC because they are still referenced from somewhere.
So where is that somewhere?
Recall the old knowledge: Everything declared in the program has its own lifecycle.
When a memory area on the Heap (called memory area A) is no longer needed, but is still referenced by another variable (called variable B), it proves that the lifecycle of variable B is longer than that of memory area A. Therefore, B has not been reclaimed, making memory area A also unable to be reclaimed by GC.
To be more specific, let's take an example
binding?.buttonSimulateMemoryLeak?.setOnClickListener {
// obviously this is a memory leak
// instance is a static variable declared in somewhere
instance = this
Handler(handlerThread3.looper).post {
// simulate memory leak
// this is harder to find
val fragment = this@SimpleFragment
while (true) {
Log.d("SimpleFragment", "Simulate Memory Leak ${System.currentTimeMillis()} $fragment")
Thread.sleep(1000)
}
}
}
In the example above, we start a Thread that has an infinite loop. In this infinite loop, we have a reference to a Fragment.
In the case where the Fragment is Destroyed, the Fragment's memory has not been reclaimed by the GC because it is still referenced from within the Thread.
Some solutions for this case
- Remember to terminate the Thread when it is no longer needed.
- Do not refer to Components with short lifecycles in an Object with a long lifecycle (or uncontrolled lifecycle)