Como descubro o uso de memória do meu aplicativo no Android?

Respostas:

1008

Observe que o uso de memória em sistemas operacionais modernos como o Linux é uma área extremamente complicada e difícil de entender. De fato, as chances de você realmente interpretar corretamente quaisquer números que conseguir são extremamente baixas. (Praticamente toda vez que olho para os números de uso de memória com outros engenheiros, sempre há uma longa discussão sobre o que eles realmente significam que só resultam em uma vaga conclusão.)

Nota: agora temos uma documentação muito mais extensa sobre o gerenciamento da memória do seu aplicativo, que abrange grande parte do material aqui e está mais atualizado com o estado do Android.

A primeira coisa é provavelmente ler a última parte deste artigo, que discute como a memória é gerenciada no Android:

Alterações na API de serviço iniciadas no Android 2.0

Agora ActivityManager.getMemoryInfo()é a nossa API de nível mais alto para analisar o uso geral da memória. Isso está presente principalmente para ajudar um aplicativo a avaliar o quão perto o sistema está de não ter mais memória para processos em segundo plano, necessitando, assim, começar a eliminar processos necessários, como serviços. Para aplicativos Java puros, isso deve ser de pouca utilidade, pois o limite de heap Java existe em parte para evitar que um aplicativo consiga forçar o sistema até esse ponto.

No nível inferior, você pode usar a API Debug para obter informações brutas no nível do kernel sobre o uso da memória: android.os.Debug.MemoryInfo

Observe que, começando com 2.0, também há uma API, ActivityManager.getProcessMemoryInfopara obter essas informações sobre outro processo: ActivityManager.getProcessMemoryInfo (int [])

Isso retorna uma estrutura MemoryInfo de baixo nível com todos esses dados:

    /** The proportional set size for dalvik. */
    public int dalvikPss;
    /** The private dirty pages used by dalvik. */
    public int dalvikPrivateDirty;
    /** The shared dirty pages used by dalvik. */
    public int dalvikSharedDirty;

    /** The proportional set size for the native heap. */
    public int nativePss;
    /** The private dirty pages used by the native heap. */
    public int nativePrivateDirty;
    /** The shared dirty pages used by the native heap. */
    public int nativeSharedDirty;

    /** The proportional set size for everything else. */
    public int otherPss;
    /** The private dirty pages used by everything else. */
    public int otherPrivateDirty;
    /** The shared dirty pages used by everything else. */
    public int otherSharedDirty;

Mas, como o que é a diferença entre Pss, PrivateDirtye SharedDirty... bem, agora a diversão começa.

Muita memória no Android (e sistemas Linux em geral) é realmente compartilhada entre vários processos. Portanto, quanta memória um processo usa não é realmente clara. Adicione ainda mais a paginação em disco (sem falar na troca que não usamos no Android) e fica ainda menos claro.

Portanto, se você pegar toda a RAM física realmente mapeada para cada processo e adicionar todos os processos, provavelmente terminará com um número muito maior que a RAM total real.

O Pssnúmero é uma métrica que o kernel calcula que leva em consideração o compartilhamento de memória - basicamente cada página de RAM em um processo é dimensionada por uma proporção do número de outros processos que também usam essa página. Dessa forma, você pode (em teoria) adicionar os pss em todos os processos para ver a RAM total que eles estão usando e comparar pss entre processos para obter uma idéia aproximada de seu peso relativo.

A outra métrica interessante aqui é PrivateDirty, que é basicamente a quantidade de RAM dentro do processo que não pode ser paginada em disco (não é suportada pelos mesmos dados em disco) e não é compartilhada com outros processos. Outra maneira de olhar para isso é a RAM que ficará disponível para o sistema quando esse processo for encerrado (e provavelmente submergiu rapidamente em caches e outros usos).

Essas são as APIs do SDK para isso. No entanto, há mais que você pode fazer como desenvolvedor com seu dispositivo.

Usando adb, há muitas informações que você pode obter sobre o uso de memória de um sistema em execução. Um comando comum é o comando adb shell dumpsys meminfoque fornecerá várias informações sobre o uso da memória de cada processo Java, contendo as informações acima e várias outras coisas. Você também pode usar o nome ou o pid de um único processo para ver, por exemplo, adb shell dumpsys meminfo systemme dê o processo do sistema:

** MEMINFO no pid 890 [sistema] **
                    dalvik nativo outro total
            tamanho: 10940 7047 N / A 17987
       atribuído: 8943 5516 N / A 14459
            grátis: 336 1531 N / A 1867
           (Pss): 4585 9282 11916 25783
  (compartilhado sujo): 2184 3596 916 6696
    (priv sujo): 4504 5956 7456 17916

 Objetos
           Visualizações: 149
     AppContexts: 13 Atividades: 0
          Ativos: 4 Ativos gerentes: 4
   Fichários locais: 141 Fichários proxy: 158
Destinatários da Morte: 49
 Soquetes OpenSSL: 0

 SQL
            heap: 205 dbArquivos: 0
       numPagers: 0 inactivePageKB: 0
    activePageKB: 0

A seção superior é a principal, onde sizeé o tamanho total no espaço de endereço de um heap específico, allocatedé o kb de alocações reais que o heap pensa que possui, freeé o kb restante livre do heap para alocações adicionais psse priv dirtysão os mesmos conforme discutido anteriormente, específico para páginas associadas a cada um dos montes.

Se você só quer olhar para o uso de memória em todos os processos, você pode usar o comando adb shell procrank. A saída disso no mesmo sistema é semelhante a:

  PID Vss Rss Pss Uss cmdline
  890 84456K 48668K 25850K 21284K system_server
 1231 50748K 39088K 17587K 13792K com.android.launcher2
  947 34488K 28528K 10834K 9308K com.android.wallpaper
  987 26964K 26956K 8751K 7308K com.google.process.gapps
  954 24300K ​​24296K 6249K 4824K com.android.phone
  948 23020K 23016K 5864K 4748K com.android.inputmethod.latin
  888 25728K 25724K 5774K 3668K zigoto
  977 24100K 24096K 5667K 4340K android.process.acore
...
   59 336K 332K 99K 92K / sistema / bin / installd
   60 396K 392K 93K 84K / sistema / bin / keystore
   51 280K 276K 74K 68K / sistema / bin / servicemanager
   54 256K 252K 69K 64K / sistema / bin / depuradord

Aqui, as colunas Vsse Rsssão basicamente ruídos (esses são o espaço de endereço direto e o uso de RAM de um processo, onde se você adicionar o uso de RAM nos processos, obtém um número ridiculamente grande).

Pssé como vimos antes, e Ussé Priv Dirty.

É interessante notar aqui: Psse Usssão um pouco (ou mais que um pouco) diferentes do que vimos meminfo. Por que é que? Bem, procrank usa um mecanismo de kernel diferente para coletar seus dados do que meminfofaz, e eles fornecem resultados ligeiramente diferentes. Por que é que? Honestamente, eu não tenho idéia. Eu acredito que procrankpode ser o mais preciso ... mas, na verdade, isso deixa o ponto: "pegue qualquer informação de memória obtida com um grão de sal; geralmente um grão muito grande".

Finalmente, há o comando adb shell cat /proc/meminfoque fornece um resumo do uso geral de memória do sistema. Há muitos dados aqui, apenas os primeiros números que vale a pena discutir (e os demais são entendidos por poucas pessoas, e minhas perguntas sobre essas poucas pessoas sobre eles geralmente resultam em explicações conflitantes):

Total de mem: 395144 kB
MemFree: 184936 kB
Tampões: 880 kB
Em cache: 84104 kB
SwapCached: 0 kB

MemTotal é a quantidade total de memória disponível para o kernel e o espaço do usuário (geralmente menor que a RAM física real do dispositivo, pois parte dessa RAM é necessária para o rádio, os buffers de DMA, etc.).

MemFreeé a quantidade de RAM que não está sendo usada. O número que você vê aqui é muito alto; normalmente em um sistema Android, isso teria apenas alguns MB, já que tentamos usar a memória disponível para manter os processos em execução

Cachedé a RAM que está sendo usada para caches do sistema de arquivos e outras coisas. Os sistemas típicos precisarão ter aproximadamente 20 MB para evitar problemas de paginação; o assassino de falta de memória do Android é ajustado para um sistema específico para garantir que os processos em segundo plano sejam eliminados antes que a RAM em cache seja consumida demais por eles para resultar em tal paginação.

hackbod
fonte
1
Ter um olhar para pixelbeat.org/scripts/ps_mem.py que utiliza as técnicas acima mencionadas para mostrar RAM usado para programas
pixelbeat
17
Muito bom escrito! Escrevi um post sobre gerenciamento de memória e uso de diferentes ferramentas para inspecionar seu uso de heap aqui macgyverdev.blogspot.com/2011/11/… se alguém achar útil.
Johan Norén
3
O que exatamente são as duas colunas "dalvik" e "nativa"?
dacongy
1
O que exatamente são "nativos" "dalvik" "outros"? No meu aplicativo, "outro" é muito grande? como posso reduzi-lo?
Landry
Eu posso usar "adb shell dumpsys meminfo", mas "adb shell procrank” me diga "/ system / bin / sh: procrank: not found". Não faço idéia. Desejo que você possa me ajudar.
Hugo
79

Sim, você pode obter informações de memória programaticamente e decidir se deve fazer um trabalho intensivo de memória.

Obtenha o tamanho de heap da VM ligando para:

Runtime.getRuntime().totalMemory();

Obtenha memória VM alocada chamando:

Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

Obtenha o Limite de tamanho de heap da VM chamando:

Runtime.getRuntime().maxMemory()

Obter memória alocada nativa chamando:

Debug.getNativeHeapAllocatedSize();

Eu criei um aplicativo para descobrir o comportamento do OutOfMemoryError e monitorar o uso da memória.

https://play.google.com/store/apps/details?id=net.coocood.oomresearch

Você pode obter o código fonte em https://github.com/coocood/oom-research

coocood
fonte
7
Esse tempo de execução retornará o uso de memória pelo processo atual ou pela pilha geral do sistema?
Mahendran
1
@mahemadhi do JavaDoc do método totalMemory () "Retorna a quantidade total de memória que está disponível para o programa em execução"
Alex
Esta não é uma resposta correta para a pergunta. A resposta não é sobre uma aplicação específica.
Amir Rezazadeh
52

Este é um trabalho em andamento, mas é isso que eu não entendo:

ActivityManager activityManager = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);

Log.i(TAG, " memoryInfo.availMem " + memoryInfo.availMem + "\n" );
Log.i(TAG, " memoryInfo.lowMemory " + memoryInfo.lowMemory + "\n" );
Log.i(TAG, " memoryInfo.threshold " + memoryInfo.threshold + "\n" );

List<RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();

Map<Integer, String> pidMap = new TreeMap<Integer, String>();
for (RunningAppProcessInfo runningAppProcessInfo : runningAppProcesses)
{
    pidMap.put(runningAppProcessInfo.pid, runningAppProcessInfo.processName);
}

Collection<Integer> keys = pidMap.keySet();

for(int key : keys)
{
    int pids[] = new int[1];
    pids[0] = key;
    android.os.Debug.MemoryInfo[] memoryInfoArray = activityManager.getProcessMemoryInfo(pids);
    for(android.os.Debug.MemoryInfo pidMemoryInfo: memoryInfoArray)
    {
        Log.i(TAG, String.format("** MEMINFO in pid %d [%s] **\n",pids[0],pidMap.get(pids[0])));
        Log.i(TAG, " pidMemoryInfo.getTotalPrivateDirty(): " + pidMemoryInfo.getTotalPrivateDirty() + "\n");
        Log.i(TAG, " pidMemoryInfo.getTotalPss(): " + pidMemoryInfo.getTotalPss() + "\n");
        Log.i(TAG, " pidMemoryInfo.getTotalSharedDirty(): " + pidMemoryInfo.getTotalSharedDirty() + "\n");
    }
}

Por que o PID não é mapeado para o resultado em activityManager.getProcessMemoryInfo ()? Claramente, você deseja tornar os dados resultantes significativos. Por que o Google tornou tão difícil correlacionar os resultados? O sistema atual nem funciona bem se eu quiser processar todo o uso da memória, pois o resultado retornado é uma matriz de objetos android.os.Debug.MemoryInfo, mas nenhum desses objetos realmente diz a que pids eles estão associados. Se você simplesmente passar uma série de todos os pids, não terá como entender os resultados. Pelo que entendi, é inútil passar mais de um pid de cada vez e, se for esse o caso, por que fazê-lo para que activityManager.getProcessMemoryInfo () use apenas uma matriz int?

Ryan Beesley
fonte
2
Eles provavelmente estão na mesma ordem que a matriz de entrada.
Taer
2
Parece uma maneira muito não intuitiva de fazer as coisas. Sim, provavelmente é esse o caso, mas como é isso de alguma maneira OOP?
quer
6
A API foi projetada para oferecer eficiência, não facilidade de uso ou simplicidade. Isso não é algo que 99% dos aplicativos devam tocar, então a eficiência é o objetivo de design mais importante.
hackbod
2
Justo. Estou tentando escrever uma ferramenta interna para rastrear o uso de memória para um ou mais dos aplicativos que estamos escrevendo. Como resultado, estou procurando uma maneira de fazer esse monitoramento enquanto afeta menos os outros processos, mas ainda sendo o mais detalhado possível com os resultados (pós-processamento). Iterar sobre os processos e, em seguida, fazer chamadas para cada processo parece ser ineficiente, supondo que haja alguma sobrecarga para cada chamada .getProcessMemoryInfo. Se a matriz retornada estiver na mesma ordem que a chamada, processarei os resultados às cegas e assumirei a paridade.
Ryan Beesley
5
Esse é um problema menor, mas para o Log, não é necessário adicionar um avanço de linha, que é tratado por você.
21411 Thomas
24

O Hackbod's é uma das melhores respostas no Stack Overflow. Lança luz sobre um assunto muito obscuro. Isto me ajudou bastante.

Outro recurso realmente útil é este vídeo imperdível: Google I / O 2011: Gerenciamento de memória para aplicativos Android


ATUALIZAR:

Process Stats, um serviço para descobrir como o aplicativo gerencia a memória explicada na postagem do blog Stats do Processo: Entendendo como o aplicativo usa a RAM de Dianne Hackborn:

Xavi Gil
fonte
19

O Android Studio 0.8.10+ introduziu uma ferramenta incrivelmente útil chamada Monitor de Memória .

insira a descrição da imagem aqui

Para que serve:

  • Mostrando memória disponível e usada em um gráfico e eventos de coleta de lixo ao longo do tempo.
  • Testando rapidamente se a lentidão do aplicativo pode estar relacionada a eventos excessivos de coleta de lixo.
  • Testar rapidamente se as falhas do aplicativo podem estar relacionadas à falta de memória.

insira a descrição da imagem aqui

Figura 1. Forçando um evento de GC (Garbage Collection) no Android Memory Monitor

Você pode obter muitas informações boas sobre o consumo de RAM em tempo real do seu aplicativo usando-o.

Machado
fonte
16

1) Acho que não, pelo menos não do Java.
2)

ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
MemoryInfo mi = new MemoryInfo();
activityManager.getMemoryInfo(mi);
Log.i("memory free", "" + mi.availMem);
yanchenko
fonte
1
alteração (ActivityManager activityManager = (ActivityManager) getSystemService (ACTIVITY_SERVICE);) em vez de (ActivityManager activityManager = = (ActivityManager) getSystemService (ACTIVITY_SERVICE);)
Rajkamal
7

Descobrimos que todas as formas padrão de obter a memória total do processo atual têm alguns problemas.

  • Runtime.getRuntime().totalMemory(): retorna apenas memória JVM
  • ActivityManager.getMemoryInfo(), Process.getFreeMemory()E qualquer outra coisa com base em /proc/meminfo- retorna informação de memória sobre todos os processos combinados (por exemplo android_util_Process.cpp )
  • Debug.getNativeHeapAllocatedSize()- usos mallinfo()que retornam informações sobre alocações de memória executadas apenas por malloc()e funções relacionadas (consulte android_os_Debug.cpp )
  • Debug.getMemoryInfo()- faz o trabalho, mas é muito lento. Demora cerca de 200 ms no Nexus 6 para uma única chamada. A sobrecarga de desempenho torna essa função inútil para nós, como a chamamos regularmente e todas as chamadas são bastante visíveis (consulte android_os_Debug.cpp )
  • ActivityManager.getProcessMemoryInfo(int[])- chama Debug.getMemoryInfo()internamente (consulte ActivityManagerService.java )

Por fim, acabamos usando o seguinte código:

const long pageSize = 4 * 1024; //`sysconf(_SC_PAGESIZE)`
string stats = File.ReadAllText("/proc/self/statm");
var statsArr = stats.Split(new [] {' ', '\t', '\n'}, 3);

if( statsArr.Length < 2 )
    throw new Exception("Parsing error of /proc/self/statm: " + stats);

return long.Parse(statsArr[1]) * pageSize;

Retorna a métrica VmRSS . Você pode encontrar mais detalhes sobre isso aqui: um , dois e três .


PS : Observei que o tema ainda não possui um trecho de código real e simples de como estimar o uso de memória privada do processo, se o desempenho não for um requisito crítico:

Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
Debug.getMemoryInfo(memInfo);
long res = memInfo.getTotalPrivateDirty();

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 
    res += memInfo.getTotalPrivateClean(); 

return res * 1024L;
Dmitry Shesterkin
fonte
1

Há muitas respostas acima que definitivamente o ajudarão, mas (após 2 dias de recursos e pesquisas em ferramentas de memória adb) , acho que também posso ajudar com minha opinião .

Como Hackbod diz: Portanto, se você pegasse toda a RAM física realmente mapeada para cada processo e adicione todos os processos, provavelmente terminaria com um número muito maior que a RAM total real. portanto, não há como obter quantidade exata de memória por processo.

Mas você pode se aproximar disso por alguma lógica .. e eu direi como ..

Há alguns API como android.os.Debug.MemoryInfoe ActivityManager.getMemoryInfo()mencionado acima, que você já deve ter sido lido sobre e usado, mas vou falar sobre outra maneira

Então, primeiro você precisa ser um usuário root para fazer funcionar. Entre no console com privilégio root executando suno processo e obtenha seuoutput and input stream . Em seguida, passe id\n (enter) no fluxo de saída e escreva-o para processar a saída. Se obter um fluxo de entrada contendo uid=0, você é usuário root.

Agora, aqui está a lógica que você usará no processo acima

Quando você obtém ouputstream do processo, você comando (procrank, dumpsys meminfo etc ...) com\n vez de id e obtenha inputstreame leia, armazene o fluxo em bytes [], char [] etc. use dados brutos .. e você está feito !!!!!

permissão:

<uses-permission android:name="android.permission.FACTORY_TEST"/>

Verifique se você é usuário root:

// su command to get root access
Process process = Runtime.getRuntime().exec("su");         
DataOutputStream dataOutputStream = 
                           new DataOutputStream(process.getOutputStream());
DataInputStream dataInputStream = 
                           new DataInputStream(process.getInputStream());
if (dataInputStream != null && dataOutputStream != null) {
   // write id to console with enter
   dataOutputStream.writeBytes("id\n");                   
   dataOutputStream.flush();
   String Uid = dataInputStream.readLine();
   // read output and check if uid is there
   if (Uid.contains("uid=0")) {                           
      // you are root user
   } 
}

Execute seu comando com su

Process process = Runtime.getRuntime().exec("su");         
DataOutputStream dataOutputStream = 
                           new DataOutputStream(process.getOutputStream());
if (dataOutputStream != null) {
 // adb command
 dataOutputStream.writeBytes("procrank\n");             
 dataOutputStream.flush();
 BufferedInputStream bufferedInputStream = 
                     new BufferedInputStream(process.getInputStream());
 // this is important as it takes times to return to next line so wait
 // else you with get empty bytes in buffered stream 
 try {
       Thread.sleep(10000);
 } catch (InterruptedException e) {                     
       e.printStackTrace();
 }
 // read buffered stream into byte,char etc.
 byte[] bff = new byte[bufferedInputStream.available()];
 bufferedInputStream.read(bff);
 bufferedInputStream.close();
 }
}

logcat: resultado

Você obtém dados brutos em uma única cadeia de caracteres do console, em vez de em qualquer instância de qualquer API, que é complexa para armazenar, pois você precisará separá-los manualmente .

Esta é apenas uma tentativa, por favor, sugira-me se eu perdi alguma coisa

Iamat8
fonte