Asignación de memoria entre procesos

La plataforma de Android se basa en la premisa de que la memoria disponible es memoria desperdiciada, de modo que intenta utilizar toda la memoria disponible en todo momento. Por ejemplo, el sistema guarda las apps en la memoria después de que se cierran para que el usuario pueda volver a abrirlas con rapidez. Por esta razón, a menudo, los dispositivos Android funcionan con muy poca memoria libre. La administración de la memoria es vital para asignar memoria de forma adecuada a los procesos importantes del sistema y a las diferentes apps del usuario.

En esta página, se analizan los aspectos básicos del modo en que Android asigna memoria al sistema y a las apps del usuario. También se explica cómo reacciona el sistema operativo ante situaciones de poca memoria.

Tipos de memoria

Los dispositivos Android contienen tres tipos de memoria: RAM, zRAM y de almacenamiento. Ten en cuenta que tanto la CPU como la GPU acceden a la misma RAM.

Tipos de memoria

Figura 1: Tipos de memoria: RAM, zRAM y de almacenamiento

  • La memoria RAM es el tipo de memoria más rápida, pero suele tener un tamaño limitado. Los dispositivos de alta gama suelen tener la mayor cantidad de RAM.

  • La memoria zRAM es una partición de la RAM utilizada para el espacio de intercambio. Cuando se colocan en zRAM, se comprimen todos los elementos; luego, se descomprimen cuando se los saca de allí. El tamaño de esta parte de la RAM aumenta o disminuye cuando se agregan páginas a zRAM o se quitan páginas de esta. Los fabricantes de dispositivos pueden establecer el tamaño máximo.

  • El almacenamiento contiene todos los datos persistentes, como el sistema de archivos y el código de objeto incluido para todas las apps, las bibliotecas y la plataforma. El almacenamiento tiene mucha más capacidad que los otros dos tipos de memoria. En Android, la memoria de almacenamiento no se usa para intercambiar espacio como en otras implementaciones de Linux, ya que la escritura frecuente puede desgastar esta memoria y acortar la duración del medio de almacenamiento.

Páginas de memoria

La memoria RAM está dividida en páginas. Por lo general, cada página tiene 4 KB de memoria.

Las páginas se consideran libres o usadas. Las páginas libres son memoria RAM sin usar. Las páginas usadas son RAM que el sistema utiliza de forma activa y se agrupan en las siguientes categorías:

  • Almacenamiento en caché: Es la memoria respaldada por un archivo del almacenamiento (por ejemplo, código o archivos asignados a la memoria). Hay dos tipos de memoria caché:
    • Privada: Es propiedad de un proceso y no se comparte.
      • Limpia: Es la copia no modificada de un archivo en almacenamiento; kswapd puede borrarla si necesita aumentar la memoria libre.
      • No sincronizada: Es la copia modificada del archivo almacenado; kswapd puede transferirla a zRAM o comprimirla en esta a fin de aumentar la memoria libre.
    • Compartida: La usan varios procesos.
      • Limpia: Es la copia no modificada de un archivo en almacenamiento; kswapd puede borrarla a fin de aumentar la memoria libre.
      • No sincronizada: Es la copia modificada del archivo en almacenamiento; permite que los cambios se vuelvan a escribir en el archivo almacenado para aumentar la memoria libre mediante kswapd o de manera explícita usando msync() o munmap().
  • Anónima: Es la memoria no respaldada por un archivo en almacenamiento (por ejemplo, asignada por mmap() con el conjunto de marcas MAP_ANONYMOUS).
    • No sincronizada: kswapd puede moverla o comprimirla para aumentar la memoria libre.

Las proporciones de páginas libres y usadas varían con el tiempo, a medida que el sistema administra la memoria RAM de manera activa. Los conceptos presentados en esta sección son clave para administrar situaciones de poca memoria. En la siguiente sección de este documento, se explican con más detalle.

Administración de memoria baja

Android tiene dos mecanismos principales para controlar situaciones de poca memoria: el daemon de intercambio de kernel y el optimizador de poca memoria.

Daemon de intercambio de kernel

El daemon de intercambio de kernel (kswapd) es parte del kernel de Linux y convierte la memoria usada en memoria libre. Se activa el daemon cuando queda poca memoria libre en el dispositivo. El kernel de Linux mantiene umbrales de memoria libre mínimos y máximos. Cuando la memoria libre cae por debajo del umbral mínimo, kswapd comienza a reclamar memoria. Una vez que la memoria libre alcanza el umbral máximo, kswapd deja de reclamar.

kswapd puede reclamar páginas limpias borrándolas porque están respaldadas por el almacenamiento y no se modificaron. Si un proceso intenta abordar una página limpia que se borró, el sistema copia la página del almacenamiento y la coloca en la memoria RAM. Esta operación se conoce como paginación por demanda.

Página limpia, respaldada por almacenamiento, borrada

Figura 2: Página limpia, respaldada por almacenamiento, borrada

kswapd puede mover páginas no sincronizadas privadas en caché y páginas no sincronizadas anónimas a zRAM, donde se comprimen. Esto hace que se libere memoria disponible en RAM (páginas libres). Si un proceso intenta tocar una página no sincronizada en zRAM, se descomprime la página y vuelve a la RAM. Si se cierra el proceso asociado con una página comprimida, esta se borra de zRAM.

Si la cantidad de memoria libre es inferior a un umbral determinado, el sistema comienza a cerrar procesos.

Página no sincronizada trasladada a zRAM y comprimida

Figura 3: Página sucia trasladada a zRAM y comprimida

Optimizador de poca memoria

Muchas veces, kswapd no puede liberar suficiente memoria para el sistema. En este caso, el sistema usa onTrimMemory() a fin de notificar a una app que se está agotando la memoria y que debería reducir sus asignaciones. Si esto no es suficiente, el kernel comienza a cerrar procesos para liberar memoria. Para lograrlo, usa el optimizador de poca memoria (LMK).

A los efectos de decidir qué proceso finalizar, LMK usa una puntuación de "memoria insuficiente" llamada oom_adj_score para priorizar los procesos en ejecución. Los procesos con una puntuación alta son los primeros en cerrarse. Primero se deben cerrar las apps en segundo plano, mientras que los procesos del sistema son los últimos que se cierran. En la siguiente tabla, se enumeran las categorías de puntuación de LMK en orden descendente. Primero se cerrarán los procesos de la categoría de mayor puntuación, que se encuentran en la fila uno:

Procesos de Android; puntuaciones altas en la parte superior

Figura 4: Procesos de Android, con puntuaciones altas en la parte superior y bajas en la parte inferior

Las siguientes son descripciones de las diversas categorías de la tabla de arriba:

  • Apps en segundo plano: Apps que se ejecutaron y que por el momento no están activas. El LMK cerrará primero las apps en segundo plano, comenzando por la de mayor oom_adj_score.

  • App anterior: Es la app en segundo plano que se usó más recientemente. La app anterior tiene mayor prioridad (una puntuación más baja) que las apps en segundo plano porque es más probable que el usuario la elija en lugar de utilizar una de las apps en segundo plano.

  • App de inicio: Esta es la app del selector. Si la cierras, desaparecerá el fondo de pantalla.

  • Servicios: Las aplicaciones inician los servicios, que pueden incluir la sincronización o la carga en la nube.

  • Apps perceptibles: Se trata de apps que no están en primer plano pero que son perceptibles para el usuario de alguna manera, como ejecutar un proceso de búsqueda que muestra una IU pequeña o escuchar música.

  • App en primer plano: Es la app que se está utilizando en ese momento. Cerrar la app en primer plano parece una falla que podría indicarle al usuario que algo funciona mal en el dispositivo.

  • (Servicios) persistentes: Son servicios principales para el dispositivo, como la telefonía y el Wi-Fi.

  • Sistema: Son los procesos del sistema. A medida que se cierran estos procesos, es posible que parezca que el teléfono se reinicia.

  • Nativo: Se trata de procesos de muy bajo nivel que usa el sistema (por ejemplo, kswapd).

Los fabricantes de dispositivos pueden cambiar el comportamiento del LMK.

Cómo calcular el uso de memoria

El kernel realiza un seguimiento de todas las páginas de memoria del sistema.

Páginas usadas por diferentes procesos

Figura 5: Páginas usadas por diferentes procesos

Cuando el sistema determina cuánta memoria utiliza una app, debe tener en cuenta las páginas compartidas. Las apps que accedan al mismo servicio o a la misma biblioteca compartirán páginas de memoria. Por ejemplo, es posible que los Servicios de Google Play y una app de juegos compartan un servicio de ubicación. Esto dificulta la tarea de determinar cuánta memoria pertenece al servicio en general y cuánta a cada app.

Páginas compartidas por dos apps

Figura 6: Páginas compartidas por dos apps (centro)

Para determinar el uso de memoria de una app, se puede usar cualquiera de las siguientes métricas:

  • Tamaño del conjunto residente (RSS): Es la cantidad de páginas compartidas y no compartidas que utiliza la app.
  • Tamaño del conjunto proporcional (PSS): Es la cantidad de páginas no compartidas que utiliza la app y una distribución uniforme de las páginas compartidas (por ejemplo, si tres procesos comparten 3 MB, cada uno obtiene 1 MB en PSS).
  • Tamaño del conjunto único (USS): Es la cantidad de páginas no compartidas que utiliza la app (las páginas compartidas no están incluidas).

El PSS es útil para el sistema operativo cuando necesita determinar cuánta memoria usan todos los procesos, ya que las páginas no se cuentan varias veces. El cálculo del PSS es lento porque el sistema debe determinar qué páginas se comparten y cuántos procesos las comparten. El RSS no distingue entre páginas compartidas y no compartidas (lo que hace que el cálculo sea más rápido) y es mejor para hacer un seguimiento de los cambios en la asignación de memoria.

Recursos adicionales