Descripción general de la administración de memoria

Android Runtime (ART) y la máquina virtual Dalvik usan las funciones de paginación y mapeo de memoria (mmapping) para administrar la memoria. Esto significa que cualquier memoria que modifique una app, ya sea asignando objetos nuevos o tocando páginas con mapeo de memoria, permanece en la memoria RAM y no se puede transferir a un almacenamiento auxiliar. La única forma de liberar memoria de una app es liberar referencias a objetos que la app contiene a fin de que la memoria esté disponible para el recolector de elementos no utilizados. Hay una excepción: cualquier archivo que se mapee sin modificación, como el código, se puede quitar de la RAM si el sistema desea usar esa memoria en otro lugar.

En esta página, se explica cómo administra Android los procesos de las apps y la asignación de memoria. Para obtener más información sobre cómo administrar la memoria de manera más eficiente en tu app, consulta Cómo administrar la memoria de tu app.

Recolección de elementos no utilizados

Un entorno de memoria administrada, como ART o la máquina virtual Dalvik, realiza un seguimiento de cada asignación de memoria. Una vez que determina que el programa ya no usa una porción de memoria, la libera de nuevo al montón sin ninguna intervención del programador. El mecanismo para reclamar memoria no utilizada dentro de un entorno de memoria administrada se conoce como recolección de elementos no utilizados. La recolección de elementos no utilizados tiene dos objetivos: encontrar objetos de datos en un programa al que no se podrá acceder en el futuro y reclamar los recursos que esos objetos usan.

El montón de memoria de Android es generacional, lo que significa que realiza un seguimiento de diferentes depósitos de asignaciones según la duración y el tamaño esperados del objeto que se está asignando. Por ejemplo, los objetos asignados recientemente pertenecen a la generación Young. Cuando un objeto permanece activo el tiempo suficiente, se puede promover a una generación anterior, seguido de una generación permanente.

Cada generación de montón tiene su propio límite máximo para la cantidad de memoria que los objetos pueden ocupar. Cada vez que una generación comienza a llenarse, el sistema ejecuta un evento de recolección de elementos no utilizados a fin de liberar memoria. La duración de la recolección de elementos no utilizados depende de qué generación de objetos se está recolectando y cuántos objetos activos hay en cada generación.

Aunque la recolección de elementos no utilizados puede ser bastante rápida, puede afectar el rendimiento de tu app. Por lo general, no es posible controlar cuándo ocurre un evento de recolección de elementos no utilizados desde tu código. El sistema tiene un conjunto de criterios para determinar cuándo realizar la recolección de elementos no utilizados. Cuando se cumplen los criterios, el sistema deja de ejecutar el proceso y comienza la recolección de elementos no utilizados. Si la recolección de elementos no utilizados se realiza en medio de un ciclo de procesamiento intensivo, como una animación o durante la reproducción de música, puede aumentar el tiempo de procesamiento. Este aumento puede impulsar la ejecución de código en tu app más allá del umbral recomendado de 16 ms para una representación de fotogramas eficiente y uniforme.

Además, tu flujo de código puede realizar tipos de trabajo que obliguen a que los eventos de recolección de elementos no utilizados ocurran con más frecuencia o que duren más de lo normal. Por ejemplo, si asignas varios objetos en la parte más interna de un bucle for durante cada fotograma de una animación alfa, quizá contamines el montón de memoria con muchos objetos. En esa circunstancia, el recolector de elementos no utilizados ejecuta múltiples eventos de recolección y puede disminuir el rendimiento de tu app.

Para obtener información más general sobre la recolección de elementos no utilizados, consulta Recolección de elementos no utilizados.

Cómo compartir memoria

Con el objetivo de adaptarse a todo lo que necesita en la memoria RAM, Android intenta compartir páginas de memoria RAM en los procesos. Puede hacerlo de las siguientes maneras:

  • Se bifurca cada proceso de la app desde un proceso existente llamado Zygote. El proceso Zygote comienza cuando el sistema se inicia y carga los recursos y el código del marco de trabajo común (como los temas de actividad). Para iniciar un nuevo proceso de la app, el sistema bifurca el proceso Zygote y, luego, carga y ejecuta el código de la app en el nuevo proceso. Este enfoque permite que se compartan en todos los procesos de la app la mayoría de las páginas de memoria RAM asignadas para el código del marco de trabajo y los recursos.
  • La mayoría de los datos estáticos se mapean en un proceso. Esta técnica permite compartir los datos entre procesos y también permite transferirlos a un almacenamiento auxiliar cuando sea necesario. Los ejemplos de datos estáticos incluyen el código Dalvik (colocándolo en un archivo .odex previamente vinculado para mapeo de memoria directo); los recursos de app (diseñando la tabla de recursos como una estructura a la que se pueda mapear memoria y alineando las entradas zip del APK); y los elementos tradicionales del proyecto, como el código nativo en archivos .so.
  • En muchos lugares, Android comparte la misma memoria RAM dinámica en todos los procesos que utilizan regiones de memoria compartida asignadas explícitamente (ya sea con ashmem o gralloc). Por ejemplo, las superficies de las ventanas usan memoria compartida entre la app y el compositor de pantalla, y los búferes del cursor usan memoria compartida entre el proveedor de contenido y el cliente.

Debido al uso extensivo de la memoria compartida, determinar la cantidad de memoria que usa la app requiere cuidado. Las técnicas para determinar correctamente el uso de la memoria de tu app se analizan en Cómo investigar el uso de tu memoria RAM.

Cómo asignar y reclamar memoria de la app

El montón de Dalvik está restringido a un solo rango de memoria virtual para cada proceso de la app. Define el tamaño lógico del montón, que se puede ampliar según sea necesario, pero solo hasta un límite que el sistema define para cada app.

El tamaño lógico del montón no es el mismo que la cantidad de memoria física que usa el montón. Al inspeccionar el montón de tu app, Android calcula un tamaño llamado tamaño del conjunto proporcional (Proportional Set Size, PSS), que incluye las páginas sucias y limpias que se comparten con otros procesos, pero solo en proporción a la cantidad de apps que comparten esa memoria RAM. Este total (PSS) es lo que el sistema considera tu huella de memoria física. Para obtener más información sobre PSS, consulta la guía Cómo investigar el uso de tu memoria RAM.

El montón de Dalvik no compacta el tamaño lógico del montón, lo que significa que Android no desfragmenta el montón para reducir el espacio. Android solo puede reducir el tamaño lógico del montón cuando hay espacio no utilizado al final del montón. Sin embargo, el sistema puede reducir la memoria física que utiliza el montón. Después de la recolección de elementos no utilizados, Dalvik recorre el montón y encuentra páginas no utilizadas; luego, devuelve esas páginas al kernel usando madvise. Por lo tanto, las asignaciones vinculadas y las desasignaciones de grandes fragmentos deberían dar como resultado la recuperación de toda (o casi toda) la memoria física utilizada. Sin embargo, recuperar memoria de pequeñas asignaciones puede ser mucho menos eficiente porque la página utilizada para una pequeña asignación todavía se puede compartir con otro elemento que aún no se liberó.

Cómo restringir la memoria de la app

A fin de mantener un entorno funcional multitarea, Android establece un límite estricto para el tamaño del montón de cada app. El límite exacto del tamaño del montón varía entre dispositivos según la cantidad de memoria RAM que el dispositivo tenga disponible en general. Si tu app alcanzó la capacidad máxima del montón e intenta asignar más memoria, puede recibir un OutOfMemoryError.

En algunos casos, tal vez te convenga consultar el sistema para determinar exactamente cuánto espacio del montón tienes disponible en el dispositivo actual, por ejemplo, para determinar cuántos datos es seguro guardar en una memoria caché. Puedes consultar esta cifra en el sistema llamando a getMemoryClass(). Este método muestra un número entero que indica la cantidad de megabytes disponibles para el montón de tu app.

Cómo cambiar de app

Cuando los usuarios cambian de una app a otra, Android mantiene las apps que no están en primer plano, es decir, que no son visibles para el usuario o que ejecutan un servicio en primer plano como la reproducción de música, en la memoria caché menos utilizada recientemente (LRU). Por ejemplo, cuando un usuario inicia por primera vez una app, se crea un proceso para ella; pero, cuando el usuario sale de la app, ese proceso no se cierra. El sistema mantiene el proceso en caché. Si, luego, el usuario vuelve a la app, el sistema reutiliza el proceso, lo que hace que se cambie de app con más rapidez.

Si tu app tiene un proceso en caché y retiene memoria que actualmente no necesita, puede afectar el rendimiento general del sistema (incluso cuando el usuario no la usa). A medida que el sistema se queda sin memoria, cierra los procesos en la memoria caché, comenzando con el proceso utilizado menos recientemente. El sistema también considera los procesos que retienen la mayor cantidad de memoria y puede cerrarlos para liberar espacio en la memoria RAM.

Nota: Cuando el sistema comienza a eliminar procesos en la memoria caché LRU, funciona principalmente de abajo hacia arriba. El sistema también considera qué procesos consumen más memoria y, por lo tanto, proporcionan al sistema más ganancia de memoria si se cierran. Cuanta menos memoria consuma tu app mientras esté en la lista de LRU, mayores serán sus posibilidades de permanecer en la lista y poder reanudarse rápidamente.

Para obtener más información sobre cómo los procesos se almacenan en caché mientras no se ejecutan en primer plano y cómo Android decide cuáles se pueden cerrar, consulta la guía Procesos y subprocesos.