web-dev-qa-db-fra.com

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

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

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

767
Andrea Baccega

Notez que l'utilisation de la mémoire sur les systèmes d'exploitation modernes tels que Linux est une zone extrêmement compliquée et difficile à comprendre. En fait, les chances que vous interprétiez correctement les chiffres obtenus sont extrêmement faibles. (À peu près à chaque fois que j'examine 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 en réalité et qui n'aboutit qu'à une vague conclusion.)

Remarque: nous disposons désormais d'une documentation beaucoup plus complète sur Gestion de la mémoire de votre application , qui couvre la majeure partie du contenu ici et qui est plus à jour avec l'état d'Android. .

La première chose à faire est probablement de lire la dernière partie de cet article qui traite de la gestion de la mémoire sous Android:

modifications de l'API de service commençant par Android 2.

Maintenant, ActivityManager.getMemoryInfo() est notre API de niveau supérieur permettant d'analyser l'utilisation globale de la mémoire. Ceci est principalement là pour aider une application à évaluer à quel point le système est sur le point de ne plus avoir de mémoire pour les processus en arrière-plan, il est donc nécessaire de commencer à supprimer les processus nécessaires tels que les services. Pour les applications Java pures, cela devrait être peu utile, car la limite de segment de mémoire Java existe en partie pour éviter qu'une application ne soit en mesure de solliciter le système à ce point.

En allant au niveau inférieur, vous pouvez utiliser l'API Debug pour obtenir des informations brutes au niveau du noyau sur l'utilisation de la mémoire: Android.os.Debug.MemoryInfo

Notez qu'à partir de 2.0, il existe également une API, ActivityManager.getProcessMemoryInfo, pour obtenir cette information sur un autre processus: ActivityManager.getProcessMemoryInfo (int [])

Cela retourne 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 en ce qui concerne la différence entre Pss, PrivateDirty et SharedDirty... eh bien, le plaisir commence maintenant.

Une grande quantité de 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 les processus n’est donc pas claire. Ajoutez en plus de cette pagination sur le disque (encore moins l'échange que nous n'utilisons pas sur Android) et c'est encore moins clair.

Ainsi, si vous preniez tous les éléments physiques RAM réellement mappés dans chaque processus et que vous additionniez tous les processus, vous obtiendriez probablement un nombre beaucoup plus grand que la RAM totale réelle.

Le nombre Pss est une métrique calculée par le noyau qui prend en compte le partage de mémoire. En gros, chaque page de RAM dans un processus est mise à l'échelle par un rapport du nombre d'autres processus utilisant également cette page. De cette façon, vous pouvez (en théorie) additionner les pss de tous les processus pour voir le total RAM qu'ils utilisent, et comparer les 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 essentiellement la quantité de RAM dans le 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 pas partagée. avec d'autres processus. Une autre façon de voir cela est la RAM qui sera disponible pour le système lorsque ce processus sera interrompu (et probablement rapidement assimilée dans des caches et autres utilisations de celui-ci).

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

Avec adb, vous pouvez obtenir de nombreuses informations 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 meminfo qui va cracher un tas d’informations sur l’utilisation de la mémoire de chaque processus Java, contenant les informations ci-dessus ainsi que diverses autres choses. Vous pouvez également vous arrêter sur le nom ou le pid d'un processus unique pour voir, par exemple, adb Shell dumpsys meminfo system donnez-moi le processus système:

 ** MEMINFO dans le 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 ViewRoots: 4 
 AppContexts: 13 Activités: 0 
 Actifs: 4 AssetManagers: 4 
 Liants locaux: 141 Liants proxy: 158 
 Destinataires des décès: 49 
 OpenSSL Sockets: 0 
 
 SQL 
 Tas: 205 dbFiles : 0 
 NumPagers: 0 inactivePageKB: 0 
 ActivePageKB: 0 

La section supérieure est la section principale, où size est la taille totale d'espace d'adressage d'un segment de mémoire particulier, allocated est le ko d'allocations réelles que le segment de mémoire pense avoir, free est le le tas restant pour le tas pour des allocations supplémentaires, et pss et priv dirty sont identiques à ceux décrits précédemment, spécifiques 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 ligne_cm .wallpaper 
 987 26964K 26956K 8751K 7308K com.google.process.gapps 
 954 24300K ​​24296K 6249K 4824K com.Android.phone 
 948 23020K 23016K 5816K 4748K. latin 
 888 25728K 25724K 5774K 3668K zygote 
 977 24100K 24096K 5667K 4340K Android.process.acore 
 ... 
 59 336K 332K 99K 92K/système/bin/installd 
 60 396K 392K 93K 84K /system/bin/keystore
 51 280K 276K 74K 68K /system/bin/servicemanager
 54 256K 252K 69K 64K/système/bin/debuggerd 

Ici, les colonnes Vss et Rss sont essentiellement du bruit (il s’agit de l’espace adresse direct et de l’utilisation RAM d’un processus, où, si vous additionnez l’utilisation de RAM processus vous obtenez un nombre ridiculement grand).

Pss est comme nous l'avons vu précédemment et Uss est Priv Dirty.

Ce qui est intéressant à noter ici: Pss et Uss sont légèrement (ou plus que légèrement) différents de ce que nous avons vu dans meminfo. Pourquoi donc? Well procrank utilise un mécanisme de noyau différent pour collecter ses données que meminfo et donne des résultats légèrement différents. Pourquoi donc? Honnêtement je n'en ai aucune idée. Je crois que procrank est peut-être la solution la plus précise ... mais, en réalité, il suffit de garder le point: "prenez toutes les informations de mémoire que vous obtenez avec un grain de sel; souvent un grain très gros."

Enfin, il y a la commande adb Shell cat /proc/meminfo qui donne un résumé de l'utilisation globale de la mémoire du système. Il y a beaucoup de données ici, seulement les premiers chiffres qui valent la peine d'être discutés (et ceux qui restent ont été compris par peu de gens, et les questions que je pose à ces quelques-uns à leur sujet entraînent souvent des explications contradictoires):

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

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, DMA tampons, etc.).

MemFree est la quantité de RAM non 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 que les processus continuent

Cached est la RAM utilisée pour les caches de système de fichiers et autres. Les systèmes typiques devront disposer d’une vingtaine de Mo ou plus pour éviter les pires états de pagination; le tueur de mémoire Android est optimisé pour un système particulier afin de s'assurer que les processus en arrière-plan sont supprimés avant que la RAM mise en cache ne soit trop consommée pour entraîner une telle pagination.

998
hackbod

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

Obtenez la taille de segment VM en appelant:

Runtime.getRuntime().totalMemory();

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

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

Obtenez VM limite de taille de segment 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 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 à l'adresse https://github.com/coocood/oom-research

74
coocood

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 ()? Il est clair que vous souhaitez rendre les données résultantes significatives. Pourquoi Google at-il rendu si difficile la corrélation des résultats? Le système actuel ne fonctionne même pas bien si je veux traiter la totalité 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 les pids auxquels ils sont associés. Si vous passez simplement dans un tableau de toutes les pids, vous n'aurez aucun moyen de comprendre les résultats. Si j'ai bien compris son utilisation, cela évite de passer plus d'un pid à la fois. Si c'est le cas, pourquoi faire en sorte que activityManager.getProcessMemoryInfo () ne prenne qu'un tableau int?

50
Ryan Beesley

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

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


METTRE À JOUR:

Traiter les statistiques, un service permettant de découvrir comment votre application gère la mémoire, comme expliqué dans l'article de blog Traiter les statistiques: comprendre comment votre application utilise RAM par Dianne Hackborn: 

24
Xavi Gil

Android Studio 0.8.10+ a introduit un outil extrêmement utile appelé Memory Monitor .

 enter image description here

A quoi ça sert:

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

 enter image description here

Figure 1. Forçage d'un événement GC (Garbage Collection) sur le moniteur de mémoire Android

Vous pouvez disposer de nombreuses informations utiles sur la consommation en temps réel RAM de votre application en l'utilisant. 

19
Machado

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);
16
yanchenko

Nous avons découvert que tous les moyens classiques d'obtenir la mémoire totale du processus en cours présentaient certains problèmes.

  • Runtime.getRuntime().totalMemory(): renvoie uniquement la mémoire de la machine virtuelle Java
  • 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() - utilise mallinfo() qui renvoie 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 travail mais c'est trop lent. Il faut environ 200ms sur 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[]) - appelle Debug.getMemoryInfo() en interne (voir ActivityManagerService.Java )

Finalement, 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 retourne la métrique VmRSS. Vous pouvez trouver plus de détails à ce sujet ici: un , deux et trois .


P.S. J'ai remarqué que le thème manquait encore d'extrait de code réel et simple expliquant comment utiliser estimation l'utilisation de la mémoire privée du processus si les performances ne sont pas essentielles:

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;
3
Dmitry Shesterkin

Dans Android studio 3.0, ils ont introduit Android-profiler pour vous aider à comprendre comment votre application utilise les ressources de processeur, de mémoire, de réseau et de batterie.

https://developer.Android.com/studio/profile/Android-profiler

 enter image description here

0
Akash Patel