Garbage Collection (GC): qué es, algoritmos y recolección de basura en el desarrollo móvil

Autor: IT Sectr Publicado: 2026-03-29 Tiempo de lectura: 10 min

La gestión automática de memoria mediante la recolección de basura es un mecanismo clave de la plataforma Android, basado en la máquina virtual ART. Según Google Android Documentation, 2026, el recolector de basura libera al desarrollador de la gestión manual de memoria, eliminando automáticamente los objetos que ya no tienen referencias. Sin GC, cada asignación de objeto requeriría una llamada explícita a free o delete, lo que en el ecosistema Java con millones de objetos por segundo es físicamente imposible.

Puntos clave

  • Garbage Collection — mecanismo automático de liberación de memoria mediante la eliminación de objetos no utilizados en Java y Android
  • Algoritmos básicos — Mark-and-Sweep, Copying Collection y Generational Collection determinan la eficiencia de la recolección
  • ART y Dalvik — dos implementaciones de la máquina virtual Android, donde ART (Android Runtime) reemplazó a Dalvik a partir de Android 5.0
  • Pausas de GC — detenciones de la ejecución de la aplicación durante la recolección — principal causa de jank y problemas de rendimiento
  • Optimización de GC — reducción de asignaciones, uso de pools de objetos y elección correcta de tipos de colección reducen la carga del recolector

¿Qué es Garbage Collection (GC)?

Garbage Collection (GC) es un proceso automático de detección y liberación de memoria ocupada por objetos que ya no son utilizados por el programa. En el contexto del desarrollo móvil, GC se utiliza en la plataforma Android a través de la máquina virtual ART, así como en la Java Virtual Machine estándar.

A diferencia de los lenguajes con gestión manual de memoria (C, C++), donde el programador debe llamar explícitamente a free o delete, GC asume completamente la tarea de rastrear el ciclo de vida de los objetos. El desarrollador crea nuevos objetos mediante el operador new, mientras que el recolector determina cuándo un objeto se vuelve inalcanzable — es decir, cuando no quedan referencias activas al mismo.

Las métricas principales de la eficiencia de GC son el tiempo de pausa (pause time) y el rendimiento (throughput). La pausa es el período durante el cual se detiene la ejecución de la aplicación para realizar la recolección. En un entorno móvil, las pausas superiores a 8–16 milisegundos son perceptibles como fotogramas perdidos (jank).

Según Google I/O 2019, ART en Android 10 redujo las pausas típicas de GC a 2–4 ms, lo que supone un 70% menos en comparación con Dalvik en Android 4.4. No obstante, la gestión inadecuada de la memoria — asignación frecuente de objetos en bucles, creación de instancias temporales innecesarias — sigue siendo la principal causa de problemas de rendimiento.

Cómo funciona el recolector de basura: algoritmos básicos

Todas las implementaciones de GC en Java y Android se basan en varios algoritmos fundamentales que se combinan para lograr un equilibrio entre el tiempo de pausa y la exhaustividad de la limpieza. Comprender estos algoritmos es esencial para escribir código compatible con GC.

Mark-and-Sweep

Mark-and-Sweep es el algoritmo más simple, que funciona en dos etapas. En la fase de Mark, el recolector recorre el grafo de objetos comenzando desde las referencias raíz (root set) — variables locales, campos estáticos, pilas de hilos. Cada objeto alcanzable se marca con un indicador de vivo. En la fase de Sweep, el recolector recorre todo el heap y libera la memoria de los objetos no marcados.

La desventaja es la fragmentación de la memoria: después de Sweep, las áreas libres se alternan con las ocupadas, dificultando la asignación de objetos grandes. En escenarios móviles, esto es crítico ya que el heap suele ser pequeño (64–512 MB en Android).

Copying Collection

Copying Collection divide el heap en dos semiespacios (semi-spaces). Los objetos activos se copian de forma compacta de un semiespacio al otro, sin huecos. Después de la copia, el semiespacio antiguo se declara completamente libre. El algoritmo elimina por completo la fragmentación, pero requiere el doble de memoria.

En entornos móviles, Copying Collection es utilizado por los recolectores generacionales para la limpieza rápida de objetos jóvenes, que estadísticamente mueren pronto (hipótesis débil generacional).

Generational Collection

Generational Collection divide el heap en generaciones: Young Generation (objetos jóvenes) y Old Generation (objetos viejos que han sobrevivido varias recolecciones). La recolección de la generación joven (Minor GC) se realiza con frecuencia y rapidez, ya que la mayoría de los objetos mueren jóvenes. La recolección de la generación vieja (Major GC o Full GC) ocurre con menos frecuencia pero dura más.

java
// Demostración de GC generacional: los objetos jóvenes mueren rápido
void processItems(List<Item> items) {
    List<Result> results = new ArrayList<>();       // vive durante todo el método
    for (Item item : items) {
        Result r = new Result(item.getValue());    // muere instantáneamente
        if (r.isValid()) {
            process(r);                               // r se convierte en basura
        }
    }
    saveResults(results);                             // results pasa a Old Gen
}

En este ejemplo, los objetos Result se crean dentro de un bucle y se convierten inmediatamente en basura — son candidatos ideales para Young GC. El objeto results vive más tiempo y migra a Old Generation. La separación de generaciones permite que Minor GC limpie objetos jóvenes en milisegundos sin tocar el heap antiguo.

Garbage Collection en Android: ART y Dalvik

Android evolucionó de Dalvik VM a ART (Android Runtime), y la implementación de GC es una de las diferencias clave entre ellos. Comprender la arquitectura de GC en Android ayuda a escribir código que minimice las pausas en dispositivos reales.

CaracterísticaDalvik (hasta 4.4)ART (5.0+)
Tipo de GCMark-and-Sweep con Concurrent MarkGeneracional + Concurrente
Pausa típica10–30 ms2–4 ms
CompactaciónNo (solo crece la fragmentación)Sí (en segundo plano, sin detener la app)
Compilación AOTJIT (Just-In-Time)AOT + JIT (híbrido)

Dalvik GC

Dalvik utilizaba una combinación de Mark-and-Sweep con una fase concurrente. Concurrent Mark permitía que la aplicación continuara funcionando durante el recorrido del grafo de objetos, pero la fase Sweep requería detener todos los hilos (Stop-The-World). En dispositivos con poca RAM (512 MB — 1 GB), las pausas alcanzaban los 30 ms, causando retrasos notables en la interfaz. Además, Dalvik no compactaba el heap, por lo que después de un uso prolongado, la fragmentación aumentaba y la asignación de objetos grandes (por ejemplo, Bitmap) podía lanzar un OutOfMemoryError incluso con suficiente memoria libre total.

ART GC

ART (Android Runtime) introdujo un recolector generacional con compactación concurrente. El heap se divide en tres regiones: Young, Mature (análogo a Old Generation) y Large Object Space (para objetos de más de 12 KB). La recolección de la región Young ocurre en paralelo sin detener los hilos en la mayoría de los casos. En Android 10+, se introdujo Concurrent Copying — la compactación se ejecuta en un hilo de fondo sin Stop-The-World.

Gracias a la arquitectura de ART, las pausas típicas de GC se redujeron a 2–4 ms, y en escenarios con predominio de objetos jóvenes — a 0.5–1 ms. Esto permitió que los dispositivos Android mantuvieran 60 FPS estables incluso durante operaciones activas de memoria.

Tipos de recolectores de basura en Java

En el ecosistema Java, existen varias implementaciones de GC, cada una con su propio perfil de rendimiento. Para el desarrollo de Android, la elección se limita a ART, pero el conocimiento de Java GC es útil al escribir código del lado del servidor para aplicaciones móviles y al desarrollar con Kotlin Multiplatform.

Serial GC

Serial GC es un recolector de un solo hilo con detención completa de la aplicación (Stop-The-World). Cada operación de Mark, Sweep y Compact se realiza con un solo hilo. El rendimiento es bajo — no se utiliza para servidores móviles. Solo es adecuado para aplicaciones pequeñas con un heap de hasta 100 MB.

Parallel GC

Parallel GC (también conocido como Throughput Collector) utiliza múltiples hilos para todas las fases de recolección. Está orientado al máximo rendimiento (throughput) — minimizando el tiempo dedicado a GC en relación con el tiempo de ejecución de la aplicación. Se activa mediante el flag -XX:+UseParallelGC en JVM.

G1 GC

G1 (Garbage-First) GC es el recolector predeterminado en Java 9+. El heap se divide en regiones de 1–32 MB. G1 predice el tiempo de pausa y se esfuerza por mantenerse dentro de un límite especificado (por defecto 200 ms). Prioridad: las regiones con mayor cantidad de basura se limpian primero (de ahí el nombre). G1 es efectivo para servidores con grandes heaps (4–64 GB) con pausas predecibles.

java
// Habilitación de G1 GC con pausa objetivo de 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");
        }
    }
}

Monitorear el heap a través de Runtime permite detectar fugas de memoria en una etapa temprana. Si used supera el 80% del heap máximo en funcionamiento estable — esto es una señal de posible fuga o consumo excesivo de memoria de la aplicación.

Problemas de GC y optimización de memoria en aplicaciones móviles

Incluso el ART GC moderno no resuelve todos los problemas — el uso inadecuado de la memoria sigue siendo la causa principal de jank y ANR (Application Not Responding). Veamos los escenarios principales y los métodos de optimización.

Pausas de GC y Jank

Las pausas de GC — detenciones de los hilos de la aplicación durante la recolección. En la pantalla, esto se manifiesta como fotogramas perdidos, cuando el tiempo entre dos fotogramas supera los 16.6 ms (60 FPS). Si GC dura 30 ms, solo se dibuja un fotograma en lugar de dos — el usuario ve tartamudeo en la interfaz.

Las principales causas de pausas largas: una gran cantidad de objetos vivos en Old Generation, fragmentación del heap, Full GC frecuente. Para el diagnóstico se utilizan Android Studio Profiler y systrace.

Reducción de la carga de GC

La regla principal del código compatible con GC es minimizar la cantidad de objetos asignados. Cada nuevo objeto requiere no solo la asignación de memoria, sino también una recolección posterior. Incluso si GC es rápido, 1000 asignaciones adicionales por segundo resultan en 1000 comprobaciones para el recolector.

  • Evite crear objetos en bucles — saque la creación fuera del bucle, reutilice variables locales
  • Use pools de objetos — para Bitmap, byte[] y otras estructuras pesadas, utilice Object Pool o RecyclerView.ViewHolder
  • Prefiera primitivas — int en lugar de Integer, float en lugar de Float evitan el autoboxing
  • Use SparseArray — en lugar de HashMap<Integer, V>, Android SDK ofrece SparseArray, LongSparseArray que trabajan con primitivas
  • StringBuilder en lugar de concatenación — cada suma de cadenas crea un nuevo objeto String

Fugas de memoria

Una fuga de memoria ocurre cuando un objeto permanece alcanzable aunque ya no sea necesario. GC no puede eliminar dicho objeto y la memoria se agota gradualmente. Causas típicas: listeners no dados de baja, referencias estáticas a Activity, clases anónimas que capturan el contexto externo y Cursor/InputStream sin cerrar.

java
// Fuga de memoria: clase anónima mantiene referencia a Activity
public void startTask() {
    new Thread(new Runnable() {                    // mantiene implícitamente this (Activity)
        @Override
        public void run() {
            // operación larga...
            System.out.println("Done");
        }
    }).start();
}

// Corrección: clase estática anidada + 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) {
            // trabajo seguro con Activity
        }
    }
}

En este ejemplo, el Runnable anónimo captura una referencia implícita a la Activity. Mientras el hilo esté vivo — Activity no puede ser recolectada por GC, incluso si el usuario ya cerró la pantalla. La corrección con WeakReference + clase estática rompe esta cadena y permite que la Activity sea liberada.

Preguntas frecuentes

¿En qué se diferencia GC en Android de GC en Java?

GC en Android (ART) es un recolector generacional con compactación concurrente, optimizado para dispositivos móviles con memoria limitada. Java GC (G1, ZGC) son recolectores del lado del servidor con grandes heaps y pausas predecibles. ART GC no utiliza flags de JVM — toda la configuración se realiza automáticamente a nivel del SO.

¿Qué es Stop-The-World en GC?

Stop-The-World es el momento en que el recolector pausa todos los hilos de la aplicación para recorrer el grafo de objetos o liberar memoria de forma segura. Cuanto más largo es el STW, más notable es el jank. ART redujo el tiempo típico de STW a 2–4 ms gracias a su arquitectura generacional.

¿Cómo detectar una fuga de memoria en Android?

Use Android Studio Memory Profiler — muestra el crecimiento del heap, la cantidad de asignaciones y permite hacer Heap Dump. Para un análisis profundo, use LeakCanary — la biblioteca detecta automáticamente fugas y muestra la cadena de referencias que impiden la recolección de GC.

¿Cuándo ocurre Full GC y por qué es peligroso?

Full GC es una recolección completa de todas las generaciones del heap, incluida Old Generation. En aplicaciones móviles, Full GC puede durar 50–200 ms, causando jank o ANR notables. Causas principales: fragmentación del heap, fugas de memoria, superación del umbral de Old Generation.

¿Cómo ayuda Kotlin a evitar fugas de memoria?

Kotlin proporciona corrutinas con concurrencia estructurada — la cancelación del ámbito cancela automáticamente todas las corrutinas hijas, evitando fugas. Kotlin también tiene el delegado lazy para inicialización diferida y funciones de ámbito que reducen el número de objetos temporales.

Resumen

  • Garbage Collection — gestión automática de memoria mediante la eliminación de objetos inalcanzables, base de Android Runtime
  • Mark-and-Sweep — algoritmo básico con recolección de dos fases, sufre de fragmentación del heap
  • Copying Collection — elimina la fragmentación copiando objetos vivos en un semiespacio compacto
  • Generational GC — divide el heap en generaciones (Young/Old), acelerando la recolección de objetos jóvenes de corta duración
  • ART en Android — recolector generacional con compactación concurrente y pausas de 2–4 ms, reemplazó a Dalvik en Android 5.0
  • Optimización de GC — reducción de asignaciones, pools de objetos, primitivas en lugar de envoltorios y SparseArray en lugar de HashMap reducen la carga del recolector
  • Diagnóstico — Android Studio Profiler, systrace y LeakCanary son las principales herramientas para identificar problemas de memoria

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también