Alocação de memória nos processos

A plataforma Android está baseada no princípio de que memória livre é memória desperdiçada. Ela tenta usar toda a memória disponível em todos os momentos. Por exemplo, o sistema mantém os apps na memória depois de terem sido fechados para que o usuário possa voltar a eles rapidamente. Por esse motivo, os dispositivos Android geralmente são executados com pouca memória livre. O gerenciamento é fundamental para uma alocação adequada da memória entre processos importantes do sistema e muitos aplicativos do usuário.

Esta página discute os conceitos básicos sobre como o Android aloca memória para o sistema e para aplicativos do usuário. Ela também explica como o sistema operacional reage a situações de pouca memória.

Tipos de memória

Os dispositivos Android contêm três tipos de memória: RAM, zRAM e de armazenamento. Observe que a CPU e a GPU acessam a mesma RAM.

Tipos de memória

Figura 1. Tipos de memória: RAM, zRAM e de armazenamento.

  • RAM é o tipo de memória mais rápido, mas geralmente é de tamanho limitado. Os dispositivos mais modernos normalmente têm mais memória RAM.

  • zRAM é uma partição da RAM usada para espaço de troca. Tudo é compactado quando colocado na zRAM e descompactado quando copiado dela. Essa parte da RAM aumenta ou diminui de tamanho conforme as páginas são movidas para a memória zRAM ou retiradas dela. Os fabricantes de dispositivos podem definir o tamanho máximo.

  • O armazenamento contém todos os dados persistentes, por exemplo, o sistema de arquivos e o código de objeto incluído para todos os apps, bibliotecas e a plataforma. O armazenamento tem uma capacidade muito maior que a dos outros dois tipos de memória. No Android, o armazenamento não é usado para espaço de troca como em outras implementações do Linux, porque a gravação frequente pode causar desgaste nessa memória e reduzir a vida útil da mídia de armazenamento.

Páginas de memória

A RAM é dividida em páginas. Normalmente, cada página tem 4 KB de memória.

As páginas são consideradas livres ou usadas. As páginas livres são RAM não usada. Páginas usadas são RAM em uso no sistema e são divididas nas categorias abaixo:

  • Em cache: memória usada por um arquivo no armazenamento, por exemplo, arquivos de código ou mapeados na memória. Há dois tipos de memória em cache:
    • Particular: pertence a um processo e não é compartilhada.
      • Limpa: cópia não modificada de um arquivo no armazenamento. Pode ser excluída por kswapd para aumentar a memória livre.
      • Suja: cópia modificada do arquivo no armazenamento. Pode ser movida ou compactada na zRAM por kswapd para aumentar a memória livre.
    • Compartilhada: usada por vários processos.
      • Limpa: cópia não modificada do arquivo no armazenamento. Pode ser excluída por kswapd para aumentar a memória livre.
      • Suja: cópia modificada do arquivo no armazenamento. Permite que mudanças sejam gravadas novamente no arquivo em armazenamento para aumentar a memória livre com kswapd ou usando explicitamente msync() ou munmap().
  • Anônima: memória não protegida por um arquivo no armazenamento (por exemplo, alocada por mmap() com a flag MAP_ANONYMOUS definida).
    • Suja: pode ser movida/compactada na zRAM por kswapd para aumentar a memória livre.

As proporções de páginas livres e usadas variam com o tempo, uma vez que o sistema gerencia ativamente a RAM. Os conceitos introduzidos nesta seção são essenciais para gerenciar situações de pouca memória. A próxima seção explica esses conceitos em mais detalhes.

Gerenciamento de pouca memória

O Android tem dois mecanismos principais para lidar com situações de pouca memória: o kernel swap daemon e o low-memory killer.

kernel swap daemon

O kernel swap daemon (kswapd) faz parte do kernel do Linux e converte a memória usada em memória livre. O daemon é ativado quando a memória livre no dispositivo fica baixa. O kernel do Linux mantém limites mínimos e máximos de memória livre. Quando a memória livre fica abaixo do limite mínimo, o kswapd começa a liberar memória. Quando a memória livre atinge o limite máximo, o kswapd interrompe a liberação de memória.

O kswapd pode excluir páginas limpas para liberar memória, porque elas têm um backup no armazenamento e não foram modificadas. Se um processo tentar acessar uma página limpa que foi excluída, o sistema copia a página do armazenamento para a RAM. Essa operação é conhecida como paginação por demanda.

Exclusão de uma página limpa com backup no armazenamento

Figura 2. Exclusão de uma página limpa com backup no armazenamento

O kswapd pode mover páginas sujas particulares e anônimas em cache para a zRAM, onde são compactadas. Isso libera memória na RAM (páginas livres). Se um processo tenta tocar em uma página suja na zRAM, a página é descompactada e movida de volta para a RAM. Se o processo associado a uma página compactada é encerrado, a página é excluída da zRAM.

Se a quantidade de memória livre ficar abaixo de um determinado limite, o sistema começa a encerrar processos.

Página suja movida para a zRAM e compactada.

Figura 3. Página suja movida para a zRAM e compactada.

Low-memory killer

Muitas vezes, o kswapd não libera memória suficiente para o sistema. Nesse caso, o sistema usa onTrimMemory() para notificar o app de que a memória está acabando e que ele deve reduzir as alocações. Se isso não for suficiente, o kernel vai começar a encerrar processos para liberar memória. Ele usa o low-memory killer (LMK) para fazer isso.

Para decidir qual processo encerrar, o LMK usa uma pontuação de "memória insuficiente" conhecida como oom_adj_score para priorizar os processos em execução. Os processos com maior pontuação são encerrados primeiro. Apps em segundo plano são os primeiros a serem encerrados, e os processos do sistema são os últimos. A tabela abaixo lista as categorias de pontuação do LMK, da maior para a menor. Os itens da categoria com maior pontuação na primeira linha são encerrados primeiro:

Processos do Android, pontuações mais altas na parte de cima

Figura 4. Processos do Android com pontuações mais altas na parte de cima, e com pontuações mais baixas na parte de baixo.

Estas são as descrições das várias categorias apresentadas na tabela acima:

  • Apps em segundo plano: apps executados anteriormente e que não estão ativos no momento. O LMK encerra primeiro os apps em segundo plano, começando por aquele com a oom_adj_score mais alta.

  • App anterior: o app em segundo plano usado mais recentemente. O app anterior tem maior prioridade (pontuação mais baixa) que os outros apps em segundo plano, porque é mais provável que o usuário mude para ele do que para outros apps em segundo plano.

  • App de início: é o app da tela de início. O encerramento desse app faz o plano de fundo desaparecer.

  • Serviços: são iniciados por aplicativos e podem incluir a sincronização ou o envio para a nuvem.

  • Apps perceptíveis: apps que não estão no primeiro plano e que são perceptíveis para o usuário de alguma forma, como executar um processo de pesquisa que mostra uma IU pequena ou ouvir música.

  • App no primeiro plano: o app usado no momento. O encerramento do app no primeiro plano é semelhante a uma falha do aplicativo que pode indicar ao usuário que algo deu errado no dispositivo.

  • Persistentes (serviços): são os serviços principais do dispositivo, como telefonia e Wi-Fi.

  • Sistema: processos do sistema. À medida que esses processos são encerrados, o smartphone pode parecer reiniciar.

  • Nativo: processos de nível muito baixo usados pelo sistema (por exemplo, kswapd).

Os fabricantes de dispositivos podem mudar o comportamento do LMK.

Calcular a ocupação da memória

O kernel rastreia todas as páginas de memória no sistema.

Páginas usadas por diferentes processos.

Figura 5. Páginas usadas por diferentes processos.

Ao determinar a quantidade de memória usada por um app, o sistema precisa considerar as páginas compartilhadas. Os apps que acessam o mesmo serviço ou biblioteca vão compartilhar páginas de memória. Por exemplo, o Google Play Services e um app de jogo podem compartilhar um serviço de localização. Isso dificulta determinar a quantidade de memória que pertence ao serviço em geral em comparação com cada aplicativo.

Páginas compartilhadas por dois apps

Figura 6. Páginas compartilhadas por dois apps (no meio)

Para determinar a ocupação da memória relacionada a um aplicativo, qualquer uma das métricas abaixo pode ser usada:

  • Resident Set Size (RSS): número de páginas compartilhadas e não compartilhadas usadas pelo app.
  • Proporcional Set Size (PSS): número de páginas não compartilhadas usadas pelo app e uma distribuição uniforme das páginas compartilhadas. Por exemplo, se três processos compartilham 3 MB, cada um deles recebe 1 MB em PSS.
  • Unique Set Size (USS): número de páginas não compartilhadas usadas pelo app (páginas compartilhadas não estão incluídas).

O PSS é útil para o sistema operacional quando ele quer saber a quantidade de memória usada por todos os processos, já que as páginas não são contadas várias vezes. O PSS leva muito tempo para ser calculado, porque o sistema precisa determinar quais páginas são compartilhadas e por quantos processos. O RSS não faz distinção entre páginas compartilhadas e não compartilhadas (o que acelera o cálculo) e é mais indicado para rastrear mudanças na alocação de memória.

Outros recursos