Fuga de memoria — uno de los problemas más insidiosos en el desarrollo móvil. El uso de memoria de la aplicación crece constantemente hasta alcanzar el límite establecido por el SO, seguido de un OutOfMemoryError o una terminación forzada. Según Square Engineering, alrededor del 40% de las aplicaciones Android tienen al menos una fuga de memoria que solo puede detectarse mediante perfilado. Analicemos las causas y los métodos para prevenir el crecimiento de la memoria.
Puntos Clave
Fuga de memoria — una situación en la que un objeto que ya no necesita la aplicación sigue siendo retenido en el heap porque una referencia activa desde el conjunto raíz (GC Root) aún apunta a él. El recolector de basura considera que dicho objeto está vivo y no lo elimina.
Hinchazón de memoria — un problema más amplio en el que la aplicación consume más memoria de la necesaria para realizar sus tareas actuales. Causas: almacenamiento en caché excesivo, duplicación de objetos, estructuras de datos subóptimas y fragmentación del heap.
En Android, cada aplicación tiene un heap limitado (generalmente 64–512 MB según el dispositivo y la versión del SO). En iOS, el límite es menos estricto, pero el sistema envía una advertencia de memoria al acercarse al umbral.
| Característica | Android | iOS |
|---|---|---|
| Límite del heap | 64–512 MB (depende del dispositivo) | Implícito (sistema) |
| Recolección de basura | ART (Concurrent, Compact) | ARC (Automatic Reference Counting) |
| Mecanismo de fuga | Referencias GC Root | Retain cycles (ciclos de referencia fuertes) |
| Resultado | OutOfMemoryError | Advertencia de memoria → terminación |
Según el Facebook Engineering Blog, las fugas de memoria causan ~15% de los informes de crash en aplicaciones móviles. En Android, esto se ve agravado por los ANR debidos a las frecuentes pausas del GC cuando la memoria es escasa.
Referencia estática a Activity — una fuga clásica de Android. Si un campo estático o singleton mantiene una referencia a una Activity, esta no será recolectada por el GC incluso después de finish() mientras el singleton esté vivo. Una Activity es un objeto pesado que contiene una jerarquía de vistas, recursos y Context.
object LeakHolder {
var activityRef: Activity ?= null // leak: static reference to Activity
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle ?= null) {
super.onCreate(savedInstanceState)
LeakHolder.activityRef = this // ❌ MainActivity will never be GC'd
}
}
Clases anónimas y lambdas — retienen implícitamente una referencia a la clase externa. Si un Runnable o Callback se pasa a un servicio externo y la Activity se destruye, el objeto de la clase anónima aún permanece en la cola e impide que la Activity sea recolectada.
En iOS, el principal problema son los retain cycles: dos objetos mantienen referencias fuertes entre sí y ARC no puede poner a cero el contador de referencias de ninguno. Un caso típico: un closure que captura self fuertemente y self que mantiene una referencia al closure.
LeakCanary — una biblioteca de Square para la detección automática de fugas en Android. Tras destruir una Activity o Fragment, comprueba si el objeto fue recolectado por el GC. Si no, hace un heap dump y muestra el trace de la fuga.
// LeakCanary 2.x — auto-integration via Application
class ExampleApplication : Application() {
override fun onCreate() {
super.onCreate()
// LeakCanary auto-installs in debug build
// via ContentProvider — zero code setup
}
}
// Force check invocation
AppWatcher.objectWatcher.watch(watchedObject, "leak description")
Android Studio Profiler — una herramienta integrada para la monitorización de memoria en tiempo real. Permite grabar un heap dump, encontrar objetos sospechosos (Retained Size > 1 MB) y rastrear la ruta GC root hasta cada objeto.
Para iOS, usa el Xcode Memory Graph Debugger. Visualiza el grafo de objetos en memoria, muestra retain cycles y permite detectar instantáneamente referencias circulares. Instruments > Allocations también está disponible para monitorización a largo plazo.
WeakReference — un mecanismo básico para referencias que no deben interferir con la recolección de basura. Si el GC decide recolectar un objeto, WeakReference devuelve null. Se usa para callbacks, listeners y referencias a componentes de UI desde hilos en segundo plano.
Componentes Lifecycle-aware — un enfoque arquitectónico implementado en Android Jetpack (Lifecycle, LiveData, Flow, coroutines). Las suscripciones se cancelan automáticamente en onDestroy, eliminando la principal clase de fugas.
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<List<User>>()
val data: LiveData<List<User>> get() = _data
fun loadData() {
viewModelScope.launch {
val result = repository.fetchData()
_data.postValue(result)
// coroutine auto-cancels on onCleared()
}
}
}
viewModelScope y lifecycleScope — CoroutineScope integrados en Android que se cancelan en el evento de ciclo de vida correspondiente. Esto elimina las fugas a través de corutinas — el escenario más común en el desarrollo moderno de Android.
Memory Profiler in Android Studio — la herramienta principal para la monitorización del heap. Muestra asignaciones en vivo, instantáneas del heap y recuentos de objetos por tipo. Permite grabar un volcado y analizarlo en MAT (Memory Analyzer Tool) para encontrar objetos sospechosos.
Eclipse MAT — un analizador de heap dump de escritorio. Tras cargar un archivo HPROF de Android Studio, MAT construye un árbol dominador, muestra el retain size de cada objeto y ofrece un análisis automático de fugas sospechosas mediante el Leak Suspects Report.
Xcode Memory Graph — un depurador visual de retain cycles. Al hacer clic en el botón Memory Graph Debugger, Xcode detiene la aplicación, construye un grafo completo de objetos en memoria y resalta los retain cycles en rojo.
| Herramienta | Plataforma | Característica |
|---|---|---|
| LeakCanary | Android | Detección automática de fugas tras destroy |
| Memory Profiler | Android Studio | Heap dump + asignaciones en vivo |
| Eclipse MAT | Android | Árbol dominador, Leak Suspects Report |
| Memory Graph | iOS (Xcode) | Visualizador de retain cycles |
Según Google I/O 2023, las aplicaciones que usan LeakCanary en builds de depuración reducen los crashes relacionados con la memoria en un 30–50% en los primeros 2 meses tras su adopción. Se recomienda añadir LeakCanary durante la fase de inicio del proyecto.
Preguntas Frecuentes
Fuga — objetos que no son accesibles por el código pero no son recolectados por el GC debido a referencias activas. Hinchazón — la aplicación mantiene objetos que son lógicamente necesarios pero en cantidad excesiva (por ejemplo, un caché de 50 MB en una aplicación en ejecución de 80 MB). La hinchazón se soluciona arquitectónicamente; la fuga, mediante una gestión correcta de referencias.
LeakCanary usa ObjectWatcher — tras onDestroy() de una Activity, crea una WeakReference a la Activity y ejecuta el GC. Si la WeakReference no se limpia tras 5 segundos, LeakCanary hace un heap dump, analiza la cadena de referencia más corta desde el GC Root hasta el objeto y muestra el stack exacto de la fuga con el archivo y la línea de código.
Bitmap ocupa memoria fuera del heap de Java en la memoria nativa (native heap). El tamaño de un Bitmap = ancho × alto × 4 bytes (ARGB_8888). Una foto de 12 MP (4000×3000) ocupa 48 MB. Android no siempre puede liberar la memoria nativa a tiempo, por lo que la acumulación de varios Bitmaps provoca OOM incluso con suficiente heap de Java.
Retain cycle — una situación en ARC donde dos objetos mantienen referencias fuertes entre sí y el contador de referencias nunca llega a cero. Un ejemplo típico: un ViewController con una referencia fuerte a un closure, y el closure captura self fuertemente. Solución: usa [weak self] o [unowned self] en los closures.
El tamaño del heap depende del dispositivo y la versión de Android. Para dispositivos antiguos (API 15–24) — 64–128 MB. Para los modernos (API 25+) — 256–512 MB. El valor exacto se puede obtener mediante ActivityManager.getMemoryClass(). Para aplicaciones grandes (juegos, editores), largeHeap=true en el manifiesto proporciona hasta 1 GB.
Resumen
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.
Lea también