¿Cómo descubro el uso de memoria de mi aplicación en Android?

Respuestas:

1008

Tenga en cuenta que el uso de memoria en sistemas operativos modernos como Linux es un área extremadamente complicada y difícil de entender. De hecho, las posibilidades de que interpretes correctamente los números que obtienes son extremadamente bajas. (Casi siempre que miro los números de uso de memoria con otros ingenieros, siempre hay una larga discusión sobre lo que realmente significan que solo da como resultado una conclusión vaga).

Nota: ahora tenemos una documentación mucho más extensa sobre la administración de la memoria de su aplicación que cubre gran parte del material aquí y está más actualizada con el estado de Android.

Lo primero es probablemente leer la última parte de este artículo que tiene una discusión sobre cómo se administra la memoria en Android:

Cambios de API de servicio que comienzan con Android 2.0

Ahora ActivityManager.getMemoryInfo()es nuestra API de más alto nivel para observar el uso general de la memoria. Esto está principalmente allí para ayudar a una aplicación a medir qué tan cerca está el sistema de no tener más memoria para los procesos en segundo plano, por lo que debe comenzar a eliminar los procesos necesarios, como los servicios. Para aplicaciones Java puras, esto debería ser de poca utilidad, ya que el límite de almacenamiento dinámico de Java está en parte para evitar que una aplicación pueda estresar el sistema hasta este punto.

Al ir a un nivel inferior, puede usar la API de depuración para obtener información sin procesar del nivel del núcleo sobre el uso de la memoria: android.os.Debug.MemoryInfo

Tenga en cuenta que a partir de 2.0 también hay una API, ActivityManager.getProcessMemoryInfopara obtener esta información sobre otro proceso: ActivityManager.getProcessMemoryInfo (int [])

Esto devuelve una estructura MemoryInfo de bajo nivel con todos estos datos:

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

Pero en cuanto a cuál es la diferencia entre Pss, PrivateDirtyy SharedDirty... bueno ahora comienza la diversión.

En realidad, se comparte mucha memoria en Android (y en los sistemas Linux en general) en múltiples procesos. Entonces, cuánta memoria usa un proceso realmente no está claro. Agregue además esa paginación al disco (y mucho menos intercambie lo que no usamos en Android) y es aún menos claro.

Por lo tanto, si tuviera que asignar toda la RAM física asignada a cada proceso y sumar todos los procesos, probablemente terminaría con un número mucho mayor que la RAM total real.

El Pssnúmero es una métrica que calcula el kernel que tiene en cuenta el uso compartido de memoria: básicamente, cada página de RAM en un proceso se escala por una proporción del número de otros procesos que también usan esa página. De esta manera, puede (en teoría) sumar los pss en todos los procesos para ver la RAM total que están utilizando y comparar los pss entre procesos para tener una idea aproximada de su peso relativo.

La otra métrica interesante aquí es PrivateDirty, que es básicamente la cantidad de RAM dentro del proceso que no se puede paginar en el disco (no está respaldada por los mismos datos en el disco) y no se comparte con ningún otro proceso. Otra forma de ver esto es la RAM que estará disponible para el sistema cuando ese proceso desaparezca (y probablemente se incorpore rápidamente en cachés y otros usos).

Eso es más o menos las API de SDK para esto. Sin embargo, hay más que puede hacer como desarrollador con su dispositivo.

Utilizando adb, hay mucha información que puede obtener sobre el uso de memoria de un sistema en ejecución. Uno común es el comando adb shell dumpsys meminfoque arrojará un montón de información sobre el uso de la memoria de cada proceso de Java, que contiene la información anterior, así como una variedad de otras cosas. También puede agregar el nombre o pid de un solo proceso para ver, por ejemplo, adb shell dumpsys meminfo systemdarme el proceso del sistema:

** MEMINFO en pid 890 [sistema] **
                    dalvik nativo otro total
            tamaño: 10940 7047 N / A 17987
       asignado: 8943 5516 N / A 14459
            gratis: 336 1531 N / A 1867
           (Pss): 4585 9282 11916 25783
  (compartido sucio): 2184 3596 916 6696
    (priv sucio): 4504 5956 7456 17916

 Objetos
           Vistas: 149 VistasRoots: 4
     AppContexts: 13 Actividades: 0
          Activos: 4 AssetManagers: 4
   Carpetas locales: 141 Carpetas de proxy: 158
Destinatarios de la muerte: 49
 Zócalos OpenSSL: 0

 SQL
            montón: 205 db Archivos: 0
       numPagers: 0 inactivePageKB: 0
    activePageKB: 0

La sección superior es la principal, donde sizeestá el tamaño total en el espacio de direcciones de un montón en particular, allocatedes el kb de asignaciones reales que el montón cree que tiene, freees el kb restante libre que el montón tiene para asignaciones adicionales, pssy priv dirtyson iguales como se discutió antes, específico para las páginas asociadas con cada uno de los montones.

Si solo quiere ver el uso de memoria en todos los procesos, puede usar el comando adb shell procrank. La salida de esto en el mismo sistema se ve así:

  PID Vss Rss Pss Uss cmdline
  890 84456K 48668K 25850K 21284K servidor_sistema
 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 cigoto
  977 24100K 24096K 5667K 4340K android.process.acore
...
   59 336K 332K 99K 92K / sistema / bin / installd
   60 396K 392K 93K 84K / system / bin / keystore
   51 280K 276K 74K 68K / sistema / bin / servicemanager
   54 256K 252K 69K 64K / sistema / bin / depurador

Aquí las columnas Vssy Rssson básicamente ruido (estos son el espacio de direcciones directo y el uso de RAM de un proceso, donde si sumas el uso de RAM en los procesos obtienes un número ridículamente grande).

Psses como hemos visto antes, y Usses Priv Dirty.

Algo interesante a tener en cuenta aquí: Pssy Ussson ligeramente (o más que ligeramente) diferentes de lo que vimos en meminfo. ¿Porqué es eso? Bueno, procrank utiliza un mecanismo de kernel diferente para recopilar sus datos que el que meminfohace, y dan resultados ligeramente diferentes. ¿Porqué es eso? Sinceramente, no tengo ni idea. Creo que procrankpuede ser el más preciso ... pero realmente, esto solo deja el punto: "toma cualquier información de memoria que obtengas con un grano de sal; a menudo un grano muy grande".

Finalmente está el comando adb shell cat /proc/meminfoque da un resumen del uso general de la memoria del sistema. Aquí hay una gran cantidad de datos, solo los primeros números que vale la pena discutir (y los restantes son entendidos por pocas personas, y mis preguntas sobre esas pocas personas sobre ellos a menudo resultan en explicaciones contradictorias):

MemTotal: 395144 kB
MemFree: 184936 kB
Tampones: 880 kB
Caché: 84104 kB
Intercambiado: 0 kB

MemTotal es la cantidad total de memoria disponible para el kernel y el espacio del usuario (a menudo menos que la RAM física real del dispositivo, ya que parte de esa RAM es necesaria para la radio, las memorias intermedias DMA, etc.).

MemFreees la cantidad de RAM que no se usa en absoluto. El número que ves aquí es muy alto; normalmente en un sistema Android, esto sería solo unos pocos MB, ya que intentamos usar la memoria disponible para mantener los procesos en ejecución

Cachedes la RAM que se usa para cachés del sistema de archivos y otras cosas similares. Los sistemas típicos necesitarán tener 20 MB más o menos para evitar que entren en mal estado de paginación; El asesino de Android sin memoria está sintonizado para un sistema en particular para asegurarse de que los procesos en segundo plano se eliminen antes de que la RAM en caché se consuma demasiado como para dar como resultado tal paginación.

hackbod
fuente
1
Eche un vistazo a pixelbeat.org/scripts/ps_mem.py que utiliza las técnicas mencionadas anteriormente para mostrar la RAM utilizada para los programas
pixelbeat
17
Muy bien escrito! Escribí una publicación sobre administración de memoria y uso de diferentes herramientas para inspeccionar el uso de su montón aquí macgyverdev.blogspot.com/2011/11/… si alguien lo encuentra útil.
Johan Norén
3
¿Qué son exactamente las dos columnas "dalvik" y "nativas"?
dacongy
1
¿Qué son exactamente los "nativos" "dalvik" "otros"? En mi aplicación, "otro" es muy grande? ¿Cómo puedo reducirlo?
landry
Puedo usar "adb shell dumpsys meminfo", pero "adb shell procrank” dime "/ system / bin / sh: procrank: not found". No tengo ni idea. Deseo que me puedan ayudar.
Hugo
79

Sí, puede obtener información de la memoria mediante programación y decidir si desea realizar un trabajo intensivo en memoria.

Obtenga VM Heap Size llamando a:

Runtime.getRuntime().totalMemory();

Obtenga memoria de máquina virtual asignada llamando a:

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

Obtenga VM Heap Size Limit llamando al:

Runtime.getRuntime().maxMemory()

Obtenga memoria asignada nativa llamando a:

Debug.getNativeHeapAllocatedSize();

Hice una aplicación para descubrir el comportamiento OutOfMemoryError y monitorear el uso de la memoria.

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

Puede obtener el código fuente en https://github.com/coocood/oom-research

Coocood
fuente
77
¿Este tiempo de ejecución devolverá el uso de memoria por el proceso actual o el montón general del sistema?
Mahendran
1
@mahemadhi del método JavaDoc de totalMemory () "Devuelve la cantidad total de memoria disponible para el programa en ejecución"
Alex
Esta no es una respuesta correcta a la pregunta. La respuesta no se trata de una aplicación específica.
Amir Rezazadeh
52

Este es un trabajo en progreso, pero esto es lo que no entiendo:

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 qué no se asigna el PID al resultado en activityManager.getProcessMemoryInfo ()? Claramente, desea que los datos resultantes sean significativos, entonces, ¿por qué Google ha hecho que sea tan difícil correlacionar los resultados? El sistema actual ni siquiera funciona bien si quiero procesar todo el uso de la memoria, ya que el resultado devuelto es una matriz de objetos android.os.Debug.MemoryInfo, pero ninguno de esos objetos realmente te dice con qué pids están asociados. Si simplemente pasa una matriz de todos los pids, no tendrá forma de comprender los resultados. Según entiendo su uso, no tiene sentido pasar más de un pedido a la vez, y si ese es el caso, ¿por qué hacerlo de modo que activityManager.getProcessMemoryInfo () solo tome una matriz int?

Ryan Beesley
fuente
2
Es probable que estén en el mismo orden que la matriz de entrada.
taer
2
Esa parece una forma muy poco intuitiva de hacer las cosas. Sí, ese es probablemente el caso, pero ¿cómo es eso de alguna manera OOP?
Ryan Beesley
66
La API fue diseñada para la eficiencia, no para la facilidad de uso o la simplicidad. Esto no es algo que el 99% de las aplicaciones deberían tocar, por lo que la eficiencia es el objetivo de diseño más importante.
hackbod
2
Lo suficientemente justo. Estoy tratando de escribir una herramienta interna para rastrear el uso de memoria para una o más de las aplicaciones que estamos escribiendo. Como resultado, estoy buscando una manera de hacer este monitoreo mientras impacta menos en los otros procesos, pero sigo siendo lo más detallado posible con los resultados (postprocesamiento). Iterar sobre los procesos y luego hacer llamadas para cada proceso parece ser ineficiente, suponiendo que haya algo de sobrecarga para cada llamada .getProcessMemoryInfo. Si se garantiza que la matriz devuelta esté en el mismo orden que la llamada, procesaré los resultados a ciegas y simplemente asumiré la paridad.
Ryan Beesley
55
Este es un problema menor, pero para Log, no es necesario agregar un salto de línea, que se maneja por usted.
ThomasW 01 de
24

Hackbod's es una de las mejores respuestas en Stack Overflow. Arroja luz sobre un tema muy oscuro. Me ayudó mucho.

Otro recurso realmente útil es este video imperdible: Google I / O 2011: administración de memoria para aplicaciones de Android


ACTUALIZAR:

Estadísticas del proceso, un servicio para descubrir cómo su aplicación administra la memoria explicada en la publicación del blog Estadísticas del proceso: Comprender cómo su aplicación usa RAM por Dianne Hackborn:

Xavi Gil
fuente
19

Android Studio 0.8.10+ ha introducido una herramienta increíblemente útil llamada Memory Monitor .

ingrese la descripción de la imagen aquí

Para qué es bueno:

  • Mostrar memoria disponible y usada en un gráfico, y eventos de recolección de basura a lo largo del tiempo.
  • Probar rápidamente si la lentitud de la aplicación podría estar relacionada con eventos excesivos de recolección de basura.
  • Probar rápidamente si los bloqueos de la aplicación pueden estar relacionados con la falta de memoria.

ingrese la descripción de la imagen aquí

Figura 1. Forzar un evento GC (Recolección de basura) en Android Memory Monitor

Puede usar mucha información buena sobre el consumo de RAM en tiempo real de su aplicación.

Machado
fuente
16

1) Supongo que no, al menos no de Java.
2)

ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
MemoryInfo mi = new MemoryInfo();
activityManager.getMemoryInfo(mi);
Log.i("memory free", "" + mi.availMem);
Yanchenko
fuente
1
cambie a (ActivityManager activityManager = (ActivityManager) getSystemService (ACTIVITY_SERVICE);) en lugar de (ActivityManager activityManager = = (ActivityManager) getSystemService (ACTIVITY_SERVICE);)
Rajkamal
7

Descubrimos que todas las formas estándar de obtener la memoria total del proceso actual tienen algunos problemas.

  • Runtime.getRuntime().totalMemory(): solo devuelve memoria JVM
  • ActivityManager.getMemoryInfo(), Process.getFreeMemory()y cualquier otra cosa basada en /proc/meminfo: devuelve información de memoria sobre todos los procesos combinados (por ejemplo, android_util_Process.cpp )
  • Debug.getNativeHeapAllocatedSize()- usos mallinfo()que devuelven información sobre asignaciones de memoria realizadas por malloc()y solo funciones relacionadas (ver android_os_Debug.cpp )
  • Debug.getMemoryInfo()- hace el trabajo pero es demasiado lento. Tarda unos 200 ms en Nexus 6 para una sola llamada. La sobrecarga de rendimiento hace que esta función sea inútil para nosotros, ya que la llamamos regularmente y cada llamada es bastante notable (consulte android_os_Debug.cpp )
  • ActivityManager.getProcessMemoryInfo(int[])- llamadas Debug.getMemoryInfo()internas (ver ActivityManagerService.java )

Finalmente, terminamos usando el siguiente 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;

Devuelve VmRSS métrica. Puede encontrar más detalles al respecto aquí: uno , dos y tres .


PD: Me di cuenta de que el tema aún carece de un fragmento de código real y simple de cómo estimar el uso de memoria privada del proceso si el rendimiento no es un 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
fuente
1

Hay muchas respuestas anteriores que definitivamente lo ayudarán, pero (después de 2 días de pagar e investigar sobre las herramientas de memoria adb) creo que puedo ayudar con mi opinión .

Como dice Hackbod: Por lo tanto, si tomara toda la RAM física realmente asignada a cada proceso y sumara todos los procesos, probablemente terminaría con un número mucho mayor que la RAM total real. así que no hay forma de que pueda obtener la cantidad exacta de memoria por proceso.

Pero puedes acercarte a él por alguna lógica ... y te diré cómo ...

Hay algunas API similares android.os.Debug.MemoryInfoy ActivityManager.getMemoryInfo()mencionadas anteriormente que quizás ya le hayan leído y utilizado, pero hablaré de otra manera

En primer lugar, debe ser un usuario root para que funcione. Ingrese a la consola con privilegio de root ejecutando suen proceso y obtenga su output and input stream. Luego pase id\n (enter) en nuestro flujo de salida y escríbalo en la salida del proceso. Si obtendrá un flujo de entrada que contenga uid=0, usted es usuario root.

Ahora aquí está la lógica que usará en el proceso anterior

Cuando obtiene el flujo de salida del proceso, pasa el comando (procrank, dumpsys meminfo, etc.) en \nlugar de id y obtiene inputstreamy lee, almacena el flujo en bytes [], char [], etc., utiliza datos sin procesar ... y usted ¡¡¡¡¡están hechos!!!!!

permiso:

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

Comprueba si eres usuario 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
   } 
}

Ejecute su 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: resultado

Obtiene datos sin procesar en una sola cadena de la consola en lugar de en algún caso de cualquier API, lo cual es complejo de almacenar, ya que deberá separarlo manualmente .

Esto es solo un intento, sugiérame si me perdí algo

Iamat8
fuente