Come posso scoprire l'utilizzo della memoria della mia applicazione in Android?


800

Come posso trovare la memoria utilizzata sulla mia applicazione Android, a livello di codice?

Spero che ci sia un modo per farlo. Inoltre, come posso ottenere anche la memoria libera del telefono?


2
O se qualcuno vuole saperne di più su tutto questo, si prega di fare riferimento a http://elinux.org/Android_Memory_Usage

1
Gestisci la memoria della tua app developer.android.com/topic/performance/memory
Shomu

Risposte:


1008

Si noti che l'utilizzo della memoria su moderni sistemi operativi come Linux è un'area estremamente complicata e di difficile comprensione. In effetti, le possibilità che tu interpreti correttamente qualsiasi numero tu ottenga è estremamente bassa. (Praticamente ogni volta che guardo i numeri di utilizzo della memoria con altri ingegneri, c'è sempre una lunga discussione su cosa significano effettivamente che si traduce solo in una vaga conclusione.)

Nota: ora abbiamo una documentazione molto più ampia sulla gestione della memoria della tua app che copre gran parte del materiale qui ed è più aggiornato con lo stato di Android.

La prima cosa è probabilmente leggere l'ultima parte di questo articolo che discute di come viene gestita la memoria su Android:

Modifiche all'API di servizio a partire da Android 2.0

Ora ActivityManager.getMemoryInfo()è la nostra API di massimo livello per esaminare l'utilizzo complessivo della memoria. Questo è principalmente lì per aiutare un utente a valutare quanto il sistema si avvicini a non avere più memoria per i processi in background, quindi è necessario iniziare a uccidere i processi necessari come i servizi. Per le applicazioni Java pure, questo dovrebbe essere di scarsa utilità, poiché il limite di heap Java è in parte lì per evitare che un'app sia in grado di stressare il sistema fino a questo punto.

Passando al livello inferiore, è possibile utilizzare l'API di debug per ottenere informazioni non elaborate a livello di kernel sull'utilizzo della memoria: android.os.Debug.MemoryInfo

Nota a partire da 2.0 c'è anche un'API, ActivityManager.getProcessMemoryInfoper ottenere queste informazioni su un altro processo: ActivityManager.getProcessMemoryInfo (int [])

Ciò restituisce una struttura MemoryInfo di basso livello con tutti questi dati:

    /** 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;

Ma da quale sia la differenza tra il Pss, PrivateDirtye SharedDirty... beh ora inizia il divertimento.

Molta memoria in Android (e sistemi Linux in generale) è in realtà condivisa tra più processi. Quindi quanta memoria utilizza un processo non è davvero chiara. Aggiungete inoltre il paging su disco (per non parlare dello scambio che non utilizziamo su Android) ed è ancora meno chiaro.

Quindi, se dovessi prendere tutta la RAM fisica effettivamente mappata su ciascun processo e sommare tutti i processi, probabilmente finiresti con un numero molto maggiore della RAM totale effettiva.

Il Pssnumero è una metrica calcolata dal kernel che tiene conto della condivisione della memoria - in pratica ogni pagina di RAM in un processo viene ridimensionata in base a un rapporto del numero di altri processi che usano anche quella pagina. In questo modo è possibile (in teoria) sommare i pss in tutti i processi per vedere la RAM totale che stanno usando e confrontare i pss tra i processi per avere un'idea approssimativa del loro peso relativo.

L'altra metrica interessante qui è PrivateDirty, che è fondamentalmente la quantità di RAM all'interno del processo che non può essere paginata su disco (non è supportata dagli stessi dati sul disco) e non è condivisa con altri processi. Un altro modo di vedere questo è la RAM che diventerà disponibile per il sistema quando quel processo andrà via (e probabilmente si riassume rapidamente in cache e altri usi di esso).

Sono praticamente le API dell'SDK per questo. Tuttavia, puoi fare di più come sviluppatore con il tuo dispositivo.

Utilizzando adb, ci sono molte informazioni che puoi ottenere sull'uso della memoria di un sistema in esecuzione. Un comando comune è il comando adb shell dumpsys meminfoche sputerà un mucchio di informazioni sull'uso della memoria di ciascun processo Java, che contiene le informazioni di cui sopra e una varietà di altre cose. Puoi anche applicare il nome o il pid di un singolo processo per vedere, ad esempio adb shell dumpsys meminfo systemdarmi il processo di sistema:

** MEMINFO in pid 890 [sistema] **
                    nativo dalvik altro totale
            taglia: 10940 7047 N / A 17987
       assegnato: 8943 5516 N / A 14459
            gratis: 336 1531 N / A 1867
           (Pss): 4585 9282 11916 25783
  (condiviso sporco): 2184 3596 916 6696
    (priv sporco): 4504 5956 7456 17916

 Oggetti
           Visualizzazioni: 149 ViewRoots: 4
     AppContexts: 13 Attività: 0
          Attività: 4 Gestione risorse: 4
   Raccoglitori locali: 141 leganti proxy: 158
Destinatari della morte: 49
 Socket OpenSSL: 0

 SQL
            heap: 205 db File: 0
       numPagers: 0 inactivePageKB: 0
    activePageKB: 0

La sezione superiore è quella principale, dove si sizetrova la dimensione totale nello spazio degli indirizzi di un particolare heap, allocatedè il kb di allocazioni effettive che l'heap ritiene abbia, freeè il rimanente kb libero che l'heap ha per allocazioni aggiuntive psse priv dirtysono gli stessi come discusso in precedenza specifico per le pagine associate a ciascuno dei cumuli.

Se vuoi solo guardare l'utilizzo della memoria in tutti i processi, puoi usare il comando adb shell procrank. L'output di questo sullo stesso sistema è simile a:

  PID Vss Rss Pss Utilizza cmdline
  890 84456K 48668K 25850K 21284K server_sistema
 1231 50748 K 39088 K 17587 K 13792 K 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 25728 K 25724 K 5774 K 3668 K zigote
  977 24100K 24096K 5667K 4340K android.process.acore
...
   59 336K 332K 99K 92K / system / bin / installd
   60 396 K 392 K 93 K 84 K / sistema / bin / archivio chiavi
   51 280K 276K 74K 68K / system / bin / servicemanager
   54 256 K 252 K 69 K 64 K / sistema / bin / debuggerd

Qui le colonne Vsse Rsssono fondamentalmente rumorose (questi sono lo spazio degli indirizzi diretto e l'utilizzo della RAM di un processo, dove se sommi l'utilizzo della RAM attraverso i processi ottieni un numero ridicolmente grande).

Pssè come abbiamo visto prima ed Ussè Priv Dirty.

Cosa interessante da notare qui: Psse Usssono leggermente (o più che leggermente) diversi da quelli che abbiamo visto meminfo. Perché? Bene procrank usa un meccanismo del kernel diverso per raccogliere i suoi dati rispetto a loro meminfo, e danno risultati leggermente diversi. Perché? Onestamente non ne ho idea. Credo che procrankpossa essere il più preciso ... ma in realtà, questo lascia solo il punto: "prendi qualsiasi informazione di memoria che ottieni con un granello di sale; spesso un granello molto grande".

Infine c'è il comando adb shell cat /proc/meminfoche fornisce un riepilogo dell'utilizzo complessivo della memoria del sistema. Ci sono molti dati qui, solo i primi numeri che vale la pena discutere (e quelli rimanenti compresi da poche persone, e le mie domande su quelle poche persone su di loro spesso portano a spiegazioni contrastanti):

MemTotal: 395144 kB
MemFree: 184936 kB
Buffer: 880 kB
Memoria cache: 84104 kB
SwapCached: 0 kB

MemTotal è la quantità totale di memoria disponibile per il kernel e lo spazio utente (spesso inferiore alla RAM fisica effettiva del dispositivo, poiché parte di tale RAM è necessaria per la radio, i buffer DMA, ecc.).

MemFreeè la quantità di RAM che non viene utilizzata affatto. Il numero che vedi qui è molto alto; in genere su un sistema Android questo sarebbe solo di pochi MB, poiché proviamo a utilizzare la memoria disponibile per mantenere i processi in esecuzione

Cachedè la RAM utilizzata per le cache del filesystem e altre cose simili. I sistemi tipici dovranno avere circa 20 MB per evitare di entrare in cattivi stati di paging; il killer di memoria esaurita di Android è ottimizzato per un particolare sistema per assicurarsi che i processi in background vengano interrotti prima che la RAM memorizzata nella cache venga consumata troppo da essi per provocare tale paging.


1
Dai un'occhiata a pixelbeat.org/scripts/ps_mem.py che utilizza le tecniche sopra menzionate per mostrare la RAM utilizzata per i programmi
pixelbeat

17
Molto bello scritto! Ho scritto un post sulla gestione della memoria e sull'uso di diversi strumenti per controllare l'utilizzo dell'heap qui macgyverdev.blogspot.com/2011/11/… se qualcuno lo trova utile.
Johan Norén,

3
Cosa sono esattamente le due colonne "dalvik" e "nativo"?
dacongy,

1
Cosa sono esattamente "nativi" "dalvik" "altri"? Nella mia app, "altro" è molto grande? come posso ridurlo?
Landry l'

Posso usare "adb shell dumpsys meminfo", ma "adb shell procrank" dimmi "/ system / bin / sh: procrank: non trovato". Non ne ho idea. Spero che tu mi possa aiutare.
Hugo

79

Sì, puoi ottenere informazioni sulla memoria a livello di codice e decidere se eseguire un lavoro intensivo in memoria.

Ottieni dimensioni heap VM chiamando:

Runtime.getRuntime().totalMemory();

Ottieni memoria VM allocata chiamando:

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

Ottieni il limite della dimensione dell'heap di VM chiamando:

Runtime.getRuntime().maxMemory()

Ottieni memoria allocata nativa chiamando:

Debug.getNativeHeapAllocatedSize();

Ho creato un'app per capire il comportamento OutOfMemoryError e monitorare l'utilizzo della memoria.

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

Puoi ottenere il codice sorgente su https://github.com/coocood/oom-research


7
Questo runtime restituirà l'utilizzo della memoria per processo corrente o heap di sistema complessivo?
Mahendran,

1
@mahemadhi da JavaDoc del metodo totalMemory () "Restituisce la quantità totale di memoria disponibile per il programma in esecuzione"
Alex

Questa non è una risposta corretta alla domanda. La risposta non riguarda un'applicazione specifica.
Amir Rezazadeh,

52

Questo è un work in progress, ma questo è ciò che non capisco:

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");
    }
}

Perché il PID non è associato al risultato in activityManager.getProcessMemoryInfo ()? Chiaramente vuoi rendere significativi i dati risultanti, quindi perché Google ha reso così difficile correlare i risultati? Il sistema attuale non funziona nemmeno bene se voglio elaborare l'intero utilizzo della memoria poiché il risultato restituito è un array di oggetti android.os.Debug.MemoryInfo, ma nessuno di questi oggetti ti dice effettivamente a quali pid sono associati. Se passi semplicemente in una matrice di tutti i pid, non avrai modo di capire i risultati. A quanto ho capito, è inutile passare più di un pid alla volta e, in tal caso, perché farlo in modo che activityManager.getProcessMemoryInfo () prenda solo un array int?


2
Probabilmente sono nello stesso ordine dell'array di input.
Taer

2
Sembra un modo molto non intuitivo di fare le cose. Sì, probabilmente è così, ma come è in qualche modo OOP?
Ryan Beesley,

6
L'API è stata progettata per efficienza, non facilità d'uso o semplicità. Questo non è qualcosa che il 99% delle app dovrebbe mai toccare, quindi l'efficienza è l'obiettivo di progettazione più importante.
hackbod,

2
Giusto. Sto cercando di scrivere uno strumento interno per tenere traccia dell'utilizzo della memoria per una o più applicazioni che stiamo scrivendo. Di conseguenza, sto cercando un modo per eseguire questo monitoraggio mentre incidono meno sugli altri processi, ma sono comunque il più dettagliato possibile con i risultati (post-elaborazione). Scorrere i processi e quindi effettuare chiamate per ogni processo sembra essere inefficiente, supponendo che ci sia un sovraccarico per ogni chiamata .getProcessMemoryInfo. Se si garantisce che l'array restituito sarà nello stesso ordine della chiamata, elaborerò i risultati alla cieca e assumerò la parità.
Ryan Beesley,

5
Questo è un problema minore, ma per Log non è necessario aggiungere un avanzamento riga gestito per te.
ThomasW,


19

Android Studio 0.8.10+ ha introdotto uno strumento incredibilmente utile chiamato Memory Monitor .

inserisci qui la descrizione dell'immagine

A cosa serve:

  • Mostra la memoria disponibile e utilizzata in un grafico e gli eventi di garbage collection nel tempo.
  • Testare rapidamente se la lentezza dell'app potrebbe essere correlata a eventi eccessivi di garbage collection.
  • Testare rapidamente se gli arresti anomali delle app possono essere correlati alla mancanza di memoria.

inserisci qui la descrizione dell'immagine

Figura 1. Forzare un evento GC (Garbage Collection) su Android Memory Monitor

Puoi avere molte buone informazioni sul consumo in tempo reale della RAM della tua app utilizzandolo.


16

1) Immagino di no, almeno non da Java.
2)

ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
MemoryInfo mi = new MemoryInfo();
activityManager.getMemoryInfo(mi);
Log.i("memory free", "" + mi.availMem);

1
passare a (ActivityManager activityManager = (ActivityManager) getSystemService (ACTIVITY_SERVICE);) anziché (ActivityManager activityManager = = (ActivityManager) getSystemService (ACTIVITY_SERVICE);)
Rajkamal,

7

Abbiamo scoperto che tutti i modi standard per ottenere la memoria totale del processo corrente presentano alcuni problemi.

  • Runtime.getRuntime().totalMemory(): restituisce solo la memoria JVM
  • ActivityManager.getMemoryInfo(), Process.getFreeMemory()E qualsiasi altra cosa in base a /proc/meminfo- rendimenti informazioni di memoria su tutti i processi combinati (ad esempio android_util_Process.cpp )
  • Debug.getNativeHeapAllocatedSize()- utilizza le mallinfo()informazioni che restituiscono informazioni sulle allocazioni di memoria eseguite esclusivamente dalle malloc()funzioni correlate (vedi android_os_Debug.cpp )
  • Debug.getMemoryInfo()- fa il lavoro ma è troppo lento. Ci vogliono circa 200ms su Nexus 6 per una singola chiamata. L'overhead delle prestazioni rende questa funzione inutile per noi poiché la chiamiamo regolarmente e ogni chiamata è abbastanza evidente (vedi android_os_Debug.cpp )
  • ActivityManager.getProcessMemoryInfo(int[])- chiama Debug.getMemoryInfo()internamente (vedi ActivityManagerService.java )

Alla fine, abbiamo finito con il seguente codice:

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;

Restituisce la metrica VmRSS . Puoi trovare maggiori dettagli qui: uno , due e tre .


PS Ho notato che il tema presenta ancora una mancanza di un frammento di codice reale e semplice su come stimare l'utilizzo della memoria privata del processo se le prestazioni non sono un requisito critico:

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;


1

Ci sono molte risposte sopra che sicuramente ti aiuteranno ma (dopo 2 giorni di permessi e ricerche sugli strumenti di memoria adb) penso di poter aiutare anche con la mia opinione .

Come dice Hackbod: Quindi se dovessi prendere tutta la RAM fisica effettivamente mappata su ciascun processo e sommare tutti i processi, probabilmente finiresti con un numero molto maggiore della RAM totale effettiva. quindi non è possibile ottenere esattamente la quantità di memoria per processo.

Ma puoi avvicinarti ad esso con un po 'di logica..e io dirò come ..

Ci sono alcune API simili android.os.Debug.MemoryInfoe ActivityManager.getMemoryInfo()menzionate sopra di cui potresti già aver letto e usato, ma parlerò in altro modo

Quindi, prima di tutto devi essere un utente root per farlo funzionare. Entra nella console con il privilegio di root eseguendo il suprocesso e ottieni il suo output and input stream. Quindi passa id\n ( invio ) in ouputstream e scrivilo per elaborare l'output, se otterrà un inputstream contenente uid=0, sei l'utente root.

Ora ecco la logica che userete nel processo sopra

Quando si arriva ouputstream del processo di passare tu comandi (procrank, dumpsys meminfo ecc ...), con \nal posto di id e ottenere la sua inputstreame leggere, memorizzare il flusso in byte [], char [] ecc .. utilizzare grezzo data..and voi sono fatti !!!!!

autorizzazione :

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

Controlla se sei un utente 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
   } 
}

Esegui il tuo comando con 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: risultato

Ottieni dati grezzi in una singola stringa dalla console anziché in qualche istanza da qualsiasi API, che è complessa da archiviare poiché dovrai separarla manualmente .

Questo è solo un tentativo, per favore, suggeriscimi se ho perso qualcosa

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.