El tiempo de ejecución de Android (ART) y la máquina virtual Dalvik utilizan la paginación y el mapeo de memoria (mmapping) para gestionar la memoria. Esto significa que cualquier memoria que una aplicación modifique -ya sea asignando nuevos objetos o tocando páginas mapeadas- permanece residente en la RAM y no puede ser paginada. La única forma de liberar memoria de una aplicación es liberar las referencias a objetos que la aplicación mantiene, poniendo la memoria a disposición del recolector de basura. Esto es con una excepción: cualquier archivo mapeado sin modificación, como el código, puede ser paginado fuera de la RAM si el sistema quiere usar esa memoria en otro lugar.
Esta página explica cómo Android gestiona los procesos de las apps y la asignación de memoria. Para más información sobre cómo gestionar la memoria de forma más eficiente en tu app, consulta Gestiona la memoria de tu app.
Recogida de basura
Un entorno de memoria gestionada, como la máquina virtual ART o Dalvik, realiza un seguimiento de cada asignación de memoria. Una vez que determina que un trozo de memoria ya no está siendo utilizado por el programa, lo libera de nuevo al montón, sin ninguna intervención del programador. El mecanismo para reclamar la memoria no utilizada en un entorno de memoria gestionada se conoce como recolección de basura. La recolección de basura tiene dos objetivos: encontrar objetos de datos en un programa a los que no se puede acceder en el futuro; y recuperar los recursos utilizados por esos objetos.
El montón de memoria de Android es generacional, lo que significa que hay diferentes cubos de asignaciones que rastrea, basados en la vida y el tamaño esperados de un objeto asignado. Por ejemplo, los objetos asignados recientemente pertenecen a la generación joven. Cuando un objeto permanece activo el tiempo suficiente, puede ser promovido a una generación más antigua, seguida de una generación permanente.
Cada generación del montón tiene su propio límite superior dedicado a 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 basura en un intento de liberar memoria. La duración de la recolección de basura depende de qué generación de objetos está recolectando y cuántos objetos activos hay en cada generación.
Aunque la recolección de basura puede ser bastante rápida, aún puede afectar el rendimiento de tu aplicación. Por lo general, no controlas cuándo se produce un evento de recolección de basura desde tu código. El sistema tiene un conjunto de criterios para determinar cuándo realizar la recolección de basura. Cuando se cumplen los criterios, el sistema deja de ejecutar el proceso y comienza la recolección de basura. Si la recolección de basura se produce en medio de un bucle de procesamiento intensivo como una animación o durante la reproducción de música, puede aumentar el tiempo de procesamiento. Este aumento puede empujar potencialmente la ejecución de código en tu aplicación más allá del umbral recomendado de 16 ms para una renderización de fotogramas eficiente y fluida.
Además, tu flujo de código puede realizar tipos de trabajo que obliguen a que los eventos de recolección de basura se produzcan con más frecuencia o hagan que duren más de lo normal. Por ejemplo, si asignas múltiples objetos en la parte más interna de un bucle for durante cada fotograma de una animación de mezcla alfa, podrías contaminar tu montón de memoria con muchos objetos. En esa circunstancia, el recolector de basura ejecuta múltiples eventos de recolección de basura y puede degradar el rendimiento de tu app.
Para obtener más información general sobre la recolección de basura, consulta Recolección de basura.
Compartir memoria
Para que quepa todo lo que necesita en la RAM, Android intenta compartir las páginas de RAM entre los procesos. Puede hacerlo de las siguientes maneras:
- Cada proceso de la app se bifurca de un proceso existente llamado Zygote. El proceso Zygote se inicia cuando el sistema arranca y carga el código y los recursos comunes del framework (como los temas de actividad). Para iniciar un nuevo proceso de aplicación, el sistema bifurca el proceso Zygote y luego carga y ejecuta el código de la aplicación en el nuevo proceso. Este enfoque permite que la mayoría de las páginas de RAM asignadas para el código y los recursos del framework se compartan entre todos los procesos de la app.
- La mayoría de los datos estáticos se mapean en un proceso. Esta técnica permite que los datos sean compartidos entre los procesos, y también permite que sean paginados cuando sea necesario. Ejemplo de datos estáticos incluyen: El código Dalvik (colocándolo en un archivo
.odex
pre-vinculado para el mmapping directo), los recursos de la app (diseñando la tabla de recursos para que sea una estructura que pueda ser mmapped 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 RAM dinámica entre los procesos utilizando regiones de memoria compartida asignadas explícitamente (ya sea con ashmem o gralloc). Por ejemplo, las superficies de las ventanas utilizan memoria compartida entre la app y el compositor de pantalla, y los búferes del cursor utilizan memoria compartida entre el proveedor de contenido y el cliente.
Debido al amplio uso de la memoria compartida, determinar cuánta memoria está utilizando tu app requiere cuidado. Las técnicas para determinar adecuadamente el uso de la memoria de tu app se discuten en Investigar el uso de la RAM.
Asignar y recuperar la memoria de la app
El heap de Dalvik está limitado a un único rango de memoria virtual para cada proceso de la app. Esto define el tamaño lógico del heap, que puede crecer como sea necesario pero sólo hasta un límite que el sistema define para cada app.
El tamaño lógico del heap no es el mismo que la cantidad de memoria física utilizada por el heap. Al inspeccionar el heap de tu app, Android calcula un valor llamado Tamaño del Conjunto Proporcional (PSS), que tiene en cuenta tanto las páginas sucias como las limpias que se comparten con otros procesos, pero sólo en una cantidad proporcional a cuántas apps comparten esa RAM. Este total (PSS) es lo que el sistema considera como su huella de memoria física. Para más información sobre la PSS, consulta la guía Investigando tu uso de la RAM.
El heap de Dalvik no compacta el tamaño lógico del heap, lo que significa que Android no desfragmenta el heap para cerrar el espacio. Android sólo puede reducir el tamaño lógico del heap cuando hay espacio no utilizado al final del heap. Sin embargo, el sistema puede reducir la memoria física utilizada por el heap. Después de la recolección de basura, Dalvik recorre el heap y encuentra las páginas no utilizadas, luego devuelve esas páginas al kernel usando madvise. Por lo tanto, las asignaciones y desasignaciones emparejadas de trozos grandes deberían dar como resultado la recuperación de toda (o casi toda) la memoria física utilizada. Sin embargo, la recuperación de memoria de las asignaciones pequeñas puede ser mucho menos eficiente porque la página utilizada para una asignación pequeña todavía puede ser compartida con algo más que aún no ha sido liberado.
Restringir la memoria de la aplicación
Para mantener un entorno multitarea funcional, Android establece un límite duro en el tamaño de la pila para cada aplicación. El límite exacto del tamaño del heap varía entre los dispositivos en función de la cantidad de RAM disponible en el dispositivo en general. Si tu app ha alcanzado la capacidad del heap e intenta asignar más memoria, puede recibir un OutOfMemoryError
.
En algunos casos, es posible que quieras consultar al sistema para determinar exactamente cuánto espacio del heap tienes disponible en el dispositivo actual; por ejemplo, para determinar cuántos datos es seguro guardar en una caché. Puedes consultar al sistema esta cifra llamando a getMemoryClass()
. Este método devuelve un número entero que indica el número de megabytes disponibles para el montón de tu aplicación.
Cambiar de aplicación
Cuando los usuarios cambian de aplicación, Android mantiene las aplicaciones 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 una caché. Por ejemplo, cuando un usuario lanza por primera vez una aplicación, se crea un proceso para ella; pero cuando el usuario abandona la aplicación, ese proceso no se cierra. El sistema mantiene el proceso en la caché. Si el usuario vuelve más tarde a la aplicación, el sistema reutiliza el proceso, haciendo así que la aplicación cambie más rápido.
Si tu aplicación tiene un proceso en caché y retiene recursos que actualmente no necesita, entonces tu aplicación -incluso mientras el usuario no la está utilizando- afecta al rendimiento general del sistema. Cuando el sistema se queda sin recursos, como la memoria, elimina los procesos de la caché. El sistema también tiene en cuenta los procesos que se aferran a la mayor parte de la memoria y puede terminarlos para liberar RAM.
Nota: Cuanto menos memoria consuma tu aplicación mientras esté en la caché, más posibilidades tendrá de no ser eliminada y de poder reanudarse rápidamente. Sin embargo, dependiendo de los requisitos instantáneos del sistema, es posible que los procesos en caché se terminen en cualquier momento sin importar su utilización de recursos.
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 matar, consulte la guía Procesos e hilos.