Automatic memory management through garbage collection is a key mechanism of the Android platform, based on the ART virtual machine. According to Google Android Documentation, 2026, the garbage collector frees developers from manual memory management, automatically removing objects that are no longer referenced. Without GC, every object allocation would require an explicit call to free or delete, which is physically impossible in the Java ecosystem with millions of objects per second.
Key Takeaways
Garbage Collection (GC) is an automatic process of detecting and freeing memory occupied by objects that are no longer used by the program. In the context of mobile development, GC is used on the Android platform through the ART virtual machine, as well as in the standard Java Virtual Machine.
Unlike languages with manual memory management (C, C++), where the programmer must explicitly call free or delete, GC completely takes on the task of tracking object lifecycles. The developer creates new objects using the new operator, while the collector determines when an object becomes unreachable — meaning no active references remain to it.
The main metrics of GC efficiency are pause time and throughput. Pause is the period during which application execution is stopped for collection. In a mobile environment, pauses longer than 8–16 milliseconds are noticeable as dropped frames (jank).
According to Google I/O 2019, ART in Android 10 reduced typical GC pauses to 2–4 ms, which is 70% less compared to Dalvik in Android 4.4. Nevertheless, improper memory management — frequent object allocation in loops, creating temporary instances unnecessarily — remains the main cause of performance issues.
All GC implementations in Java and Android are based on several fundamental algorithms that are combined to achieve a balance between pause time and collection thoroughness. Understanding these algorithms is essential for writing GC-friendly code.
Mark-and-Sweep is the simplest algorithm, working in two stages. In the Mark phase, the collector traverses the object graph starting from root references (root set) — local variables, static fields, thread stacks. Each reachable object is marked with a live flag. In the Sweep phase, the collector goes through the entire heap and frees the memory of unmarked objects.
The drawback is memory fragmentation: after Sweep, free areas alternate with occupied ones, making it difficult to allocate large objects. In mobile scenarios, this is critical since the heap is typically small (64–512 MB on Android).
Copying Collection divides the heap into two semi-spaces. Active objects are copied compactly from one semi-space to the other, without gaps. After copying, the old semi-space is entirely declared free. The algorithm completely eliminates fragmentation, but requires twice as much memory.
In mobile environments, Copying Collection is used by generational collectors for fast cleanup of young objects, which statistically die early (weak generational hypothesis).
Generational Collection divides the heap into generations: Young Generation (young objects) and Old Generation (old objects that survived several collections). Young generation collection (Minor GC) is performed frequently and quickly, since most objects die young. Old generation collection (Major GC or Full GC) occurs less frequently but takes longer.
// Demonstration of generational GC: young objects die fast
void processItems(List<Item> items) {
List<Result> results = new ArrayList<>(); // lives for the entire method
for (Item item : items) {
Result r = new Result(item.getValue()); // dies instantly
if (r.isValid()) {
process(r); // r becomes garbage
}
}
saveResults(results); // results moves to Old Gen
}
In this example, Result objects are created inside a loop and immediately become garbage — they are ideal candidates for Young GC. The results object lives longer and migrates to Old Generation. Generation separation allows Minor GC to clear young objects in milliseconds without touching the old heap.
Android evolved from Dalvik VM to ART (Android Runtime), and the GC implementation is one of the key differences between them. Understanding the GC architecture in Android helps write code that minimizes pauses on real devices.
| Characteristic | Dalvik (up to 4.4) | ART (5.0+) |
|---|---|---|
| GC Type | Mark-and-Sweep with Concurrent Mark | Generational + Concurrent |
| Typical Pause | 10–30 ms | 2–4 ms |
| Compaction | No (fragmentation grows only) | Yes (in background, without stopping the app) |
| AOT Compilation | JIT (Just-In-Time) | AOT + JIT (hybrid) |
Dalvik used a combination of Mark-and-Sweep with a concurrent phase. Concurrent Mark allowed the application to continue working during the object graph traversal, but the Sweep phase required stopping all threads (Stop-The-World). On devices with small amounts of RAM (512 MB — 1 GB), pauses reached 30 ms, causing noticeable interface lag. Additionally, Dalvik did not compact the heap, so after prolonged operation, fragmentation grew, and allocating large objects (e.g., Bitmap) could throw an OutOfMemoryError even with sufficient total free memory.
ART (Android Runtime) introduced a generational collector with concurrent compaction. The heap is divided into three regions: Young, Mature (analogous to Old Generation), and Large Object Space (for objects larger than 12 KB). Young Region collection happens in parallel without stopping threads in most cases. In Android 10+, Concurrent Copying was introduced — compaction runs in a background thread without Stop-The-World.
Thanks to ART’s architecture, typical GC pauses were reduced to 2–4 ms, and in scenarios with a predominance of young objects — to 0.5–1 ms. This allowed Android devices to maintain stable 60 FPS even during active memory operations.
In the Java ecosystem, there are several GC implementations, each with its own performance profile. For Android development, the choice is limited to ART, but knowledge of Java GC is useful when writing server-side code for mobile applications and when developing with Kotlin Multiplatform.
Serial GC is a single-threaded collector with full application stop (Stop-The-World). Each Mark, Sweep, and Compact operation is performed by one thread. Performance is low — not used for mobile servers. Only suitable for small applications with a heap of up to 100 MB.
Parallel GC (also known as Throughput Collector) uses multiple threads for all collection phases. It is oriented toward maximum throughput — minimizing the time spent on GC relative to application runtime. Enabled via the -XX:+UseParallelGC flag in JVM.
G1 (Garbage-First) GC is the default collector in Java 9+. The heap is divided into regions of 1–32 MB. G1 predicts pause time and strives to stay within a specified limit (default 200 ms). Priority: regions with the largest amount of garbage are cleaned first (hence the name). G1 is effective for servers with large heaps (4–64 GB) with predictable pauses.
// Enabling G1 GC with target pause of 100 ms
// java -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -jar app.jar
public class MemoryMonitor {
private static final long THRESHOLD = 512 * 1024 * 1024; // 512 MB
public void checkHeapUsage() {
Runtime rt = Runtime.getRuntime();
long used = rt.totalMemory() - rt.freeMemory();
if (used > THRESHOLD) {
System.out.println("Heap usage exceeded threshold: " + used);
System.out.println("Consider reducing allocations");
}
}
}
Monitoring the heap via Runtime allows detecting memory leaks at an early stage. If used exceeds 80% of the maximum heap under stable operation — this is a signal of a possible leak or excessive application memory consumption.
Even modern ART GC does not solve all problems — improper memory usage remains the main cause of jank and ANR (Application Not Responding). Let’s look at the main scenarios and optimization methods.
GC Pauses — application thread stops during collection. On screen, this manifests as dropped frames, when the time between two frames exceeds 16.6 ms (60 FPS). If GC lasts 30 ms, only one frame is drawn instead of two — the user sees interface stuttering.
The main causes of long pauses: a large number of live objects in Old Generation, heap fragmentation, frequent Full GC. Android Studio Profiler and systrace are used for diagnostics.
The main rule of GC-friendly code is to minimize the number of allocated objects. Each new object requires not only memory allocation but also subsequent collection. Even if GC is fast, 1000 extra allocations per second result in 1000 checks for the collector.
A memory leak occurs when an object remains reachable even though it is no longer needed. GC cannot delete such an object, and memory is gradually exhausted. Typical causes: unregistered listeners, static references to Activity, anonymous classes capturing external context, and unclosed Cursor/InputStream.
// Memory leak: anonymous class holds reference to Activity
public void startTask() {
new Thread(new Runnable() { // implicitly holds this (Activity)
@Override
public void run() {
// long operation...
System.out.println("Done");
}
}).start();
}
// Fix: static nested class + WeakReference
private static class TaskRunnable implements Runnable {
private WeakReference<Activity> activityRef;
TaskRunnable(Activity activity) {
this.activityRef = new WeakReference<>(activity);
}
@Override
public void run() {
Activity act = activityRef.get();
if (act != null) {
// safe work with Activity
}
}
}
In this example, the anonymous Runnable captures an implicit reference to the Activity. While the thread is alive — the Activity cannot be collected by GC, even if the user has already closed the screen. The WeakReference + static class fix breaks this chain and allows the Activity to be reclaimed.
Frequently Asked Questions
GC in Android (ART) is a generational collector with concurrent compaction, optimized for mobile devices with limited memory. Java GC (G1, ZGC) are server-side collectors with large heaps and predictable pauses. ART GC does not use JVM flags — all tuning is done automatically at the OS level.
Stop-The-World is the moment when the collector pauses all application threads to safely traverse the object graph or free memory. The longer the STW, the more noticeable the jank. ART reduced typical STW time to 2–4 ms thanks to its generational architecture.
Use Android Studio Memory Profiler — it shows heap growth, allocation count, and allows taking Heap Dumps. For in-depth analysis, use LeakCanary — the library automatically detects leaks and shows the reference chain preventing GC collection.
Full GC is a complete collection of all heap generations, including Old Generation. In mobile applications, Full GC can last 50–200 ms, causing noticeable jank or ANR. Main causes: heap fragmentation, memory leaks, exceeding the Old Generation threshold.
Kotlin provides coroutines with structured concurrency — scope cancellation automatically cancels all child coroutines, preventing leaks. Kotlin also has the lazy delegate for lazy initialization and scope functions that reduce the number of temporary objects.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also