Comment découvrir l'utilisation de la mémoire de mon application dans Android?

800

Comment trouver la mémoire utilisée sur mon application Android, par programme?

J'espère qu'il y a un moyen de le faire. De plus, comment puis-je également obtenir la mémoire libre du téléphone?

Andrea Baccega
la source
2
Ou si quelqu'un veut en savoir plus sur tout cela, veuillez consulter http://elinux.org/Android_Memory_Usage
1
Gérez la mémoire de votre application developer.android.com/topic/performance/memory
Shomu

Réponses:

1008

Notez que l'utilisation de la mémoire sur des systèmes d'exploitation modernes comme Linux est un domaine extrêmement compliqué et difficile à comprendre. En fait, les chances que vous interprétiez correctement les chiffres que vous obtenez sont extrêmement faibles. (À peu près chaque fois que je regarde les chiffres d'utilisation de la mémoire avec d'autres ingénieurs, il y a toujours une longue discussion sur ce qu'ils signifient réellement, ce qui ne donne qu'une vague conclusion.)

Remarque: nous avons maintenant une documentation beaucoup plus complète sur la gestion de la mémoire de votre application qui couvre une grande partie du matériel ici et est plus à jour avec l'état d'Android.

La première chose est probablement de lire la dernière partie de cet article qui explique comment la mémoire est gérée sur Android:

Modifications de l'API de service à partir d'Android 2.0

Voici maintenant ActivityManager.getMemoryInfo()notre API de plus haut niveau pour examiner l'utilisation globale de la mémoire. Ceci est principalement là pour aider une application à évaluer à quel point le système arrive à ne plus avoir de mémoire pour les processus d'arrière-plan, ce qui nécessite de commencer à tuer les processus nécessaires comme les services. Pour les applications Java pures, cela devrait être de peu d'utilité, car la limite de tas Java est là en partie pour éviter qu'une application ne soit en mesure de stresser le système à ce stade.

À un niveau inférieur, vous pouvez utiliser l'API de débogage pour obtenir des informations brutes au niveau du noyau sur l'utilisation de la mémoire: android.os.Debug.MemoryInfo

Notez ActivityManager.getProcessMemoryInfoqu'à partir de la version 2.0, il existe également une API, pour obtenir ces informations sur un autre processus: ActivityManager.getProcessMemoryInfo (int [])

Cela renvoie une structure MemoryInfo de bas niveau avec toutes ces données:

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

Mais quant à ce que la différence entre Pss, PrivateDirtyet SharedDirty... eh bien maintenant le plaisir commence.

Une grande partie de la mémoire dans Android (et les systèmes Linux en général) est en fait partagée entre plusieurs processus. La quantité de mémoire utilisée par un processus n'est donc pas claire. Ajoutez à cela la pagination sur le disque (et encore moins l'échange que nous n'utilisons pas sur Android) et c'est encore moins clair.

Ainsi, si vous deviez prendre toute la RAM physique réellement mappée dans chaque processus et additionner tous les processus, vous vous retrouveriez probablement avec un nombre beaucoup plus élevé que la RAM totale réelle.

Le Pssnombre est une métrique calculée par le noyau qui prend en compte le partage de mémoire - fondamentalement, chaque page de RAM dans un processus est mise à l'échelle par un ratio du nombre d'autres processus utilisant également cette page. De cette façon, vous pouvez (en théorie) additionner le pss sur tous les processus pour voir la RAM totale qu'ils utilisent et comparer le pss entre les processus pour avoir une idée approximative de leur poids relatif.

L'autre métrique intéressante ici est PrivateDirty, qui est fondamentalement la quantité de RAM à l'intérieur du processus qui ne peut pas être paginée sur le disque (elle n'est pas sauvegardée par les mêmes données sur le disque), et n'est partagée avec aucun autre processus. Une autre façon de voir les choses est la RAM qui deviendra disponible pour le système lorsque ce processus disparaîtra (et sera probablement rapidement intégrée dans les caches et autres utilisations).

C'est à peu près les API du SDK pour cela. Cependant, vous pouvez faire plus en tant que développeur avec votre appareil.

En utilisant adb, il y a beaucoup d'informations que vous pouvez obtenir sur l'utilisation de la mémoire d'un système en cours d'exécution. Une commande courante est la commande adb shell dumpsys meminfoqui crachera un tas d'informations sur l'utilisation de la mémoire de chaque processus Java, contenant les informations ci-dessus ainsi qu'une variété d'autres choses. Vous pouvez également mettre le nom ou le pid d'un seul processus à voir, par exemple, adb shell dumpsys meminfo systemdonnez-moi le processus système:

** MEMINFO dans pid 890 [système] **
                    dalvik natif autre total
            taille: 10940 7047 N / A 17987
       attribué: 8943 5516 N / A 14459
            gratuit: 336 1531 N / A 1867
           (Pss): 4585 9282 11916 25783
  (partagé sale): 2184 3596 916 6696
    (privé sale): 4504 5956 7456 17916

 Objets
           Vues: 149 VuesRacines: 4
     AppContexts: 13 Activités: 0
          Actifs: 4 AssetManagers: 4
   Liants locaux: 141 Liants proxy: 158
Destinataires du décès: 49
 Sockets OpenSSL: 0

 SQL
            tas: 205 dbFichiers: 0
       numPagers: 0 inactivePageKB: 0
    activePageKB: 0

La section supérieure est la principale, où sizeest la taille totale dans l'espace d'adressage d'un tas particulier, allocatedest le kb d'allocations réelles que le tas pense avoir, freeest le kb restant libre que le tas a pour des allocations supplémentaires, et psset priv dirtysont les mêmes comme discuté avant spécifique aux pages associées à chacun des tas.

Si vous souhaitez simplement examiner l'utilisation de la mémoire dans tous les processus, vous pouvez utiliser la commande adb shell procrank. La sortie de ceci sur le même système ressemble à:

  PID Vss Rss Pss Uss cmdline
  890 84456K 48668K 25850K 21284K serveur_système
 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 zygote
  977 24100K 24096K 5667K 4340K android.process.acore
...
   59 336K 332K 99K 92K / system / bin / installd
   60 396K 392K 93K 84K / système / bac / magasin de clés
   51 280K 276K 74K 68K / système / bac / gestionnaire de service
   54 256K 252K 69K 64K / system / bin / debuggerd

Ici, les colonnes Vsset Rsssont essentiellement du bruit (ce sont l'espace d'adressage simple et l'utilisation de la RAM d'un processus, où si vous additionnez l'utilisation de la RAM entre les processus, vous obtenez un nombre ridiculement élevé).

Pssest comme nous l'avons vu auparavant, et Ussest Priv Dirty.

Chose intéressante à noter ici: Psset Usssont légèrement (ou plus que légèrement) différents de ce que nous avons vu meminfo. Pourquoi donc? Eh bien, procrank utilise un mécanisme de noyau différent pour collecter ses données meminfo, et ils donnent des résultats légèrement différents. Pourquoi donc? Honnêtement, je n'ai aucune idée. Je crois que c'est procrankpeut-être le plus précis ... mais vraiment, cela laisse juste le point: "prenez toutes les informations de mémoire que vous obtenez avec un grain de sel; souvent un très gros grain."

Enfin, il y a la commande adb shell cat /proc/meminfoqui donne un résumé de l'utilisation globale de la mémoire du système. Il y a beaucoup de données ici, seuls les premiers chiffres méritent d'être discutés (et les autres sont compris par peu de gens, et mes questions à ces quelques personnes à leur sujet entraînent souvent des explications contradictoires):

MemTotal: 395144 kB
MemFree: 184936 kB
Tampons: 880 kB
En cache: 84104 kB
SwapCached: 0 ko

MemTotal est la quantité totale de mémoire disponible pour le noyau et l'espace utilisateur (souvent inférieure à la RAM physique réelle du périphérique, car une partie de cette RAM est nécessaire pour la radio, les tampons DMA, etc.).

MemFreeest la quantité de RAM qui n'est pas utilisée du tout. Le nombre que vous voyez ici est très élevé; généralement sur un système Android, cela ne représente que quelques Mo, car nous essayons d'utiliser la mémoire disponible pour maintenir les processus en cours

Cachedest la RAM utilisée pour les caches du système de fichiers et d'autres choses de ce type. Les systèmes typiques devront avoir environ 20 Mo pour éviter d’entrer dans de mauvais états de pagination; Android tueur de mémoire est réglé pour un système particulier afin de s'assurer que les processus d'arrière-plan sont supprimés avant que la RAM mise en cache ne soit trop consommée par eux pour entraîner une telle pagination.

hackbod
la source
1
Jetez un œil à pixelbeat.org/scripts/ps_mem.py qui utilise les techniques mentionnées ci-dessus pour afficher la RAM utilisée pour les programmes
pixelbeat
17
Très beau écrit! J'ai écrit un article sur la gestion de la mémoire et l'utilisation de différents outils pour inspecter votre utilisation du tas ici macgyverdev.blogspot.com/2011/11/… si quelqu'un le trouve utile.
Johan Norén
3
Que sont exactement les deux colonnes "dalvik" et "native"?
dacongy
1
Que sont exactement "natifs" "dalvik" "autres"? Dans mon application, "autre" est très énorme? comment puis-je le réduire?
Landry
Je peux utiliser "adb shell dumpsys meminfo", mais "adb shell procrank" tell me "/ system / bin / sh: procrank: not found". Je n'en ai aucune idée. J'espère que vous pouvez m'aider.
Hugo
79

Oui, vous pouvez obtenir des informations sur la mémoire par programme et décider d'effectuer un travail gourmand en mémoire.

Obtenez VM Heap Size en appelant:

Runtime.getRuntime().totalMemory();

Obtenez la mémoire VM allouée en appelant:

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

Obtenez VM Heap Size Limit en appelant:

Runtime.getRuntime().maxMemory()

Obtenez la mémoire allouée native en appelant:

Debug.getNativeHeapAllocatedSize();

J'ai créé une application pour comprendre le comportement de OutOfMemoryError et surveiller l'utilisation de la mémoire.

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

Vous pouvez obtenir le code source sur https://github.com/coocood/oom-research

coocood
la source
7
Ce runtime retournera-t-il l'utilisation de la mémoire par le processus en cours ou le tas système global?
Mahendran
1
@mahemadhi de la méthode JavaDoc de totalMemory () "Renvoie la quantité totale de mémoire disponible pour le programme en cours d'exécution"
Alex
Ce n'est pas une bonne réponse à la question. La réponse ne concerne pas une application spécifique.
Amir Rezazadeh
52

C'est un travail en cours, mais c'est ce que je ne comprends pas:

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

Pourquoi le PID n'est-il pas mappé au résultat dans activityManager.getProcessMemoryInfo ()? De toute évidence, vous voulez rendre les données résultantes significatives, alors pourquoi Google a-t-il rendu si difficile la corrélation des résultats? Le système actuel ne fonctionne même pas bien si je veux traiter l'intégralité de l'utilisation de la mémoire car le résultat renvoyé est un tableau d'objets android.os.Debug.MemoryInfo, mais aucun de ces objets ne vous indique réellement à quels pids ils sont associés. Si vous passez simplement un tableau de tous les pids, vous n'aurez aucun moyen de comprendre les résultats. Si je comprends bien son utilisation, cela rend inutile de passer plus d'un pid à la fois, puis si c'est le cas, pourquoi faire en sorte que activityManager.getProcessMemoryInfo () ne prenne qu'un tableau int?

Ryan Beesley
la source
2
Ils sont probablement dans le même ordre que le tableau d'entrée.
taer
2
Cela semble être une façon très non intuitive de faire les choses. Oui, c'est probablement le cas, mais comment est-ce en quelque sorte OOP?
Ryan Beesley
6
L'API a été conçue pour l'efficacité, pas pour la facilité d'utilisation ou la simplicité. Ce n'est tout simplement pas quelque chose que 99% des applications devraient toucher, donc l'efficacité est l'objectif de conception le plus important.
hackbod
2
C'est suffisant. J'essaie d'écrire un outil interne pour suivre l'utilisation de la mémoire pour une ou plusieurs des applications que nous écrivons. En conséquence, je cherche un moyen de faire ce suivi tout en impactant le moins possible les autres processus, tout en étant le plus détaillé possible avec les résultats (post-traitement). Itérer sur les processus, puis effectuer des appels pour chaque processus semble être inefficace, en supposant qu'il y a une surcharge pour chaque appel .getProcessMemoryInfo. Si le tableau renvoyé est garanti dans le même ordre que l'appel, je traiterai les résultats à l'aveugle et supposerai simplement la parité.
Ryan Beesley
5
Il s'agit d'un problème mineur, mais pour Log, il n'est pas nécessaire d'ajouter un saut de ligne, qui est géré pour vous.
ThomasW
24

Hackbod's est l'une des meilleures réponses sur Stack Overflow. Il éclaire un sujet très obscur. Cela m'a beaucoup aidé.

Une autre ressource vraiment utile est cette vidéo incontournable: Google I / O 2011: Gestion de la mémoire pour les applications Android


MISE À JOUR:

Process Stats, un service pour découvrir comment votre application gère la mémoire expliqué dans l'article de blog Process Stats: Comprendre comment votre application utilise la RAM par Dianne Hackborn:

Xavi Gil
la source
19

Android Studio 0.8.10+ a introduit un outil incroyablement utile appelé Memory Monitor .

entrez la description de l'image ici

À quoi ça sert:

  • Affichage de la mémoire disponible et utilisée dans un graphique et des événements de récupération de place dans le temps.
  • Tester rapidement si la lenteur de l'application peut être liée à des événements de collecte de déchets excessifs.
  • Tester rapidement si les plantages d'applications peuvent être liés à un manque de mémoire.

entrez la description de l'image ici

Figure 1. Forcer un événement GC (Garbage Collection) sur Android Memory Monitor

Vous pouvez avoir beaucoup de bonnes informations sur la consommation de RAM en temps réel de votre application en l'utilisant.

Machado
la source
16

1) Je suppose que non, du moins pas de Java.
2)

ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
MemoryInfo mi = new MemoryInfo();
activityManager.getMemoryInfo(mi);
Log.i("memory free", "" + mi.availMem);
yanchenko
la source
1
remplacer par (ActivityManager activityManager = (ActivityManager) getSystemService (ACTIVITY_SERVICE);) au lieu de (ActivityManager activityManager = = (ActivityManager) getSystemService (ACTIVITY_SERVICE);)
Rajkamal
7

Nous avons découvert que toutes les méthodes standard pour obtenir la mémoire totale du processus actuel ont quelques problèmes.

  • Runtime.getRuntime().totalMemory(): renvoie uniquement la mémoire JVM
  • ActivityManager.getMemoryInfo(), Process.getFreeMemory()et tout autre élément basé sur /proc/meminfo- renvoie des informations sur la mémoire de tous les processus combinés (par exemple android_util_Process.cpp )
  • Debug.getNativeHeapAllocatedSize()- utilisations mallinfo()qui retournent des informations sur les allocations de mémoire effectuées par malloc()et les fonctions associées uniquement (voir android_os_Debug.cpp )
  • Debug.getMemoryInfo()- fait le boulot mais c'est trop lent. Cela prend environ 200 ms sur le Nexus 6 pour un seul appel. La surcharge de performances rend cette fonction inutile pour nous car nous l'appelons régulièrement et chaque appel est assez perceptible (voir android_os_Debug.cpp )
  • ActivityManager.getProcessMemoryInfo(int[])- appels en Debug.getMemoryInfo()interne (voir ActivityManagerService.java )

Enfin, nous avons fini par utiliser le code suivant:

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;

Il renvoie la métrique VmRSS . Vous pouvez trouver plus de détails à ce sujet ici: un , deux et trois .


PS J'ai remarqué que le thème manque toujours d'un extrait de code réel et simple sur la façon d' estimer l'utilisation de la mémoire privée du processus si les performances ne sont pas une exigence critique:

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
la source
1

Il y a beaucoup de réponses ci-dessus qui vous aideront certainement, mais (après 2 jours de moyens et de recherches sur les outils de mémoire adb), je pense que je peux aussi aider avec mon opinion .

Comme le dit Hackbod: Ainsi, si vous deviez prendre toute la RAM physique réellement mappée dans chaque processus et additionner tous les processus, vous vous retrouveriez probablement avec un nombre beaucoup plus élevé que la RAM totale réelle. il n'y a donc aucun moyen d'obtenir la quantité exacte de mémoire par processus.

Mais vous pouvez vous en approcher par une logique .. et je vais vous dire comment ..

Il y a des API comme android.os.Debug.MemoryInfoet ActivityManager.getMemoryInfo()mentionnées ci-dessus que vous pourriez déjà avoir lues et utilisées, mais je parlerai d'une autre manière

Donc, tout d'abord, vous devez être un utilisateur root pour le faire fonctionner. Accédez à la console avec le privilège root en exécutant suin process et obtenez son output and input stream. Ensuite, passez id\n (entrez) dans ouputstream et écrivez-le pour traiter la sortie, si vous obtiendrez un flux d'entrée contenant uid=0, vous êtes l'utilisateur root.

Voici maintenant la logique que vous utiliserez dans le processus ci-dessus

Lorsque vous obtenez ouputstream de processus passez votre commande (procrank, dumpsys meminfo etc ...) avec \nau lieu de id et obtenez son inputstreamet lisez, stockez le flux en octets [], char [] etc. utilisez des données brutes .. et vous sont fait!!!!!

autorisation :

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

Vérifiez si vous êtes 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
   } 
}

Exécutez votre commande avec 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: résultat

Vous obtenez des données brutes dans une seule chaîne à partir de la console au lieu dans certains cas de n'importe quelle API, ce qui est complexe à stocker car vous devrez les séparer manuellement .

Ceci est juste un essai, veuillez me suggérer si j'ai raté quelque chose

Iamat8
la source