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 sem arquivo de backup no armazenamento (por exemplo, alocada por mmap() com a sinalização 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.

Como calcular o consumo de 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 o consumo de memória relacionado 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.

Diagnosticar jornadas ideais do usuário (CUJs) com uso intenso de memória

Ao investigar problemas de memória, é útil identificar as jornadas ideais do usuário (CUJs) com uso intenso de memória do app. Esses são fluxos de trabalho específicos que exigem uma alocação significativa de memória. As CUJs comuns com uso intenso de memória incluem:

  • Carregar e manipular mídias complexas ou bitmaps grandes.
  • Hospedar instâncias de WebView.

Se o app depende de conteúdo da Web, as instâncias de WebView podem se tornar um gargalo de memória oculto devido à arquitetura de vários processos e à execução de código nativo. Para diagnósticos específicos, práticas recomendadas de ciclo de vida e ferramentas personalizadas para conteúdo da Web, consulte Gerenciar e diagnosticar a memória do WebView.

Outros recursos