Как обнаружить использование памяти моим приложением в Android?

Как программно найти память, используемую в моем приложении для Android?

Я надеюсь, что есть способ сделать это. Плюс, как мне получить свободную память телефона тоже?

10 ответов

Решение

Обратите внимание, что использование памяти в современных операционных системах, таких как Linux, является чрезвычайно сложной и трудной для понимания областью. На самом деле шансы на то, что вы на самом деле правильно интерпретируете полученные вами цифры, крайне низки. (Практически каждый раз, когда я смотрю на цифры использования памяти с другими инженерами, всегда идет долгая дискуссия о том, что они на самом деле означают, что приводит только к расплывчатым выводам.)

Примечание: теперь у нас есть намного более обширная документация по управлению памятью вашего приложения, которая охватывает большую часть материала здесь и более соответствует состоянию Android.

Прежде всего, вероятно, стоит прочитать последнюю часть этой статьи, в которой обсуждается, как управлять памятью в Android:

Изменения API сервиса, начиная с Android 2.0

Сейчас ActivityManager.getMemoryInfo() это наш API верхнего уровня для просмотра общего использования памяти. В основном это делается для того, чтобы помочь приложению определить, насколько близко система перестает иметь память для фоновых процессов, и, таким образом, нужно начинать убивать необходимые процессы, такие как службы. Для приложений на чистом Java это должно быть бесполезно, поскольку ограничение кучи Java отчасти позволяет избежать того, чтобы одно приложение могло нагружать систему до этого уровня.

Переходя на более низкий уровень, вы можете использовать API-интерфейс отладки для получения необработанной информации об использовании памяти на уровне ядра: android.os.Debug.MemoryInfo

Обратите внимание, начиная с 2.0 есть также API, ActivityManager.getProcessMemoryInfo, чтобы получить эту информацию о другом процессе: ActivityManager.getProcessMemoryInfo (int [])

Это возвращает низкоуровневую структуру MemoryInfo со всеми этими данными:

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

Но что касается разницы между Pss, PrivateDirty, а также SharedDirty... ну теперь веселье начинается.

Большая часть памяти в Android (и системах Linux в целом) фактически распределяется между несколькими процессами. Так что, сколько памяти использует процесс, на самом деле не ясно. Добавьте поверх этой подкачки на диск (не говоря уже об обмене, который мы не используем на Android), и это еще менее понятно.

Таким образом, если бы вы взяли всю физическую оперативную память, фактически сопоставленную с каждым процессом, и суммировали все процессы, вы, вероятно, получили бы число, намного превышающее фактическую общую память.

Pss число - это метрика, вычисляемая ядром, которая учитывает совместное использование памяти - в основном каждая страница ОЗУ в процессе масштабируется по отношению к числу других процессов, также использующих эту страницу. Таким образом, вы можете (теоретически) сложить pss во всех процессах, чтобы увидеть общую оперативную память, которую они используют, и сравнить pss между процессами, чтобы получить приблизительное представление об их относительном весе.

Другая интересная метрика здесь PrivateDirty, который представляет собой объем оперативной памяти внутри процесса, который не может быть перенесен на диск (он не поддерживается теми же данными на диске) и не используется совместно с другими процессами. Еще один способ взглянуть на это - это оперативная память, которая станет доступной для системы, когда этот процесс завершится (и, вероятно, будет быстро отнесен к кэшам и другим видам ее использования).

Это в значительной степени SDK API для этого. Тем не менее, вы можете сделать больше как разработчик своего устройства.

С помощью adbВы можете получить много информации об использовании памяти работающей системой. Распространенным является команда adb shell dumpsys meminfo который выложит кучу информации об использовании памяти каждым процессом Java, содержащую вышеупомянутую информацию, а также множество других вещей. Вы также можете прикрепить имя или pid одного процесса, чтобы увидеть, например, adb shell dumpsys meminfo system дайте мне системный процесс:

** MEMINFO в pid 890 [система] ** родной dalvik другой общий размер:    10940     7047      N/A    17987 выделено:     8943     5516      N/A    14459 бесплатно:      336     1531      N/A     1867
           (Pss):     4585     9282    11916    25783
  (общий грязный):     2184     3596      916     6696
    (конфиденциально):     4504     5956     7456    17916 Просмотры объектов:      149        ViewRoots:        4
     AppContexts:       13 Деятельность:        0 Активы:        4    AssetManager:        4
   Local Binder:      141    Proxy Binder:      158 Смертельные получатели:       49
 OpenSSL Sockets:        0 Куча SQL:      205          dbFiles:        0
       numPagers:        0   inactivePageKB:        0
    activePageKB:        0

Верхний раздел является основным, где size это общий размер в адресном пространстве конкретной кучи, allocated это килобайт фактических выделений, которые, как думает куча, он имеет, free является ли оставшийся кб свободным в куче для дополнительных выделений, и pss а также priv dirty такие же, как обсуждалось ранее, специфично для страниц, связанных с каждой из куч.

Если вы просто хотите посмотреть на использование памяти во всех процессах, вы можете использовать команду adb shell procrank, Вывод этого на той же системе выглядит так:

  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 зигота
  977   24100K   24096K    5667K    4340K  android.process.acore
...
   59     336K     332K      99K      92K  /system/bin/installd
   60     396K     392K      93K      84K  / система / корзина / склад ключей
   51     280K     276K      74K      68K  / система /bin/servicemanager
   54     256K     252K      69K      64K  /system/bin/debuggerd

Здесь Vss а также Rss столбцы - это в основном шум (это прямое адресное пространство и использование оперативной памяти процесса, где, если вы сложите использование оперативной памяти между процессами, вы получите смехотворно большое количество).

Pss как мы видели раньше, и Uss является Priv Dirty,

Интересная вещь, чтобы отметить здесь: Pss а также Uss немного (или более чем немного) отличается от того, что мы видели в meminfo, Это почему? Ну, procrank использует другой механизм ядра для сбора своих данных, чем meminfo делает, и они дают немного разные результаты. Это почему? Честно говоря, я понятия не имею. я верю procrank может быть, более точный... но на самом деле, это просто оставляет смысл: "бери любую информацию о памяти, которую ты получаешь с крошкой соли; часто с очень большой крошкой"

Наконец, есть команда adb shell cat /proc/meminfo это дает сводную информацию об общем использовании памяти системой. Здесь много данных, только первые несколько цифр, которые стоит обсудить (а остальные понимают немногие, и мои вопросы о тех немногих людях о них часто приводят к противоречивым объяснениям):

MemTotal: 395144 кБ
MemFree:          184936 кБ
Буферы:             880 кБ
Кэшировано:            84104 кБ
SwapCached:            0 кБ

MemTotal это общий объем памяти, доступной ядру и пользовательскому пространству (часто меньше, чем фактическая физическая память устройства, так как часть этой памяти необходима для радиосвязи, буферов DMA и т. д.).

MemFree это объем оперативной памяти, который не используется вообще. Число, которое вы видите здесь, очень высоко; как правило, в системе Android это будет всего несколько МБ, так как мы стараемся использовать доступную память для поддержания процессов в рабочем состоянии

Cached ОЗУ используется для кэширования файловой системы и других подобных вещей. Типичные системы должны иметь 20 МБ или около того, чтобы избежать попадания в состояние плохой подкачки; Android out of memory killer настроен для конкретной системы, чтобы гарантировать, что фоновые процессы будут убиты до того, как кэшированная оперативная память израсходует ими слишком много, чтобы привести к такой подкачке.

Да, вы можете получить информацию о памяти программным путем и решить, следует ли выполнять объемную работу.

Получить размер кучи виртуальной машины можно по телефону:

Runtime.getRuntime().totalMemory();

Получить выделенную память виртуальной машины, вызвав:

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

Получить ограничение размера кучи виртуальной машины можно по телефону:

Runtime.getRuntime().maxMemory()

Получить собственную выделенную память, позвонив:

Debug.getNativeHeapAllocatedSize();

Я сделал приложение, чтобы выяснить поведение OutOfMemoryError и контролировать использование памяти.

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

Вы можете получить исходный код по адресу https://github.com/coocood/oom-research

Это работа в процессе, но это то, что я не понимаю:

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

Почему PID не сопоставляется с результатом в activityManager.getProcessMemoryInfo()? Понятно, что вы хотите сделать полученные данные значимыми, так почему же Google так сложно сопоставить результаты? Текущая система не работает даже хорошо, если я хочу обработать все использование памяти, так как возвращаемый результат является массивом объектов android.os.Debug.MemoryInfo, но ни один из этих объектов на самом деле не говорит вам, с какими pids они связаны. Если вы просто передадите массив всех pids, у вас не будет возможности понять результаты. Как я понимаю, его использование делает бессмысленным передачу более одного pid за раз, а затем, если это так, зачем делать так, чтобы activityManager.getProcessMemoryInfo () принимает только массив int?

Hackbod's - один из лучших ответов на переполнение стека. Это проливает свет на очень непонятный предмет. Это мне очень помогло.

Еще один действительно полезный ресурс - это видео, которое обязательно нужно посмотреть: Google I / O 2011: управление памятью для приложений Android


ОБНОВИТЬ:

Process Stats, сервис, позволяющий узнать, как ваше приложение управляет памятью, объясняется в посте блога. Process Stats: Понимание того, как ваше приложение использует оперативную память от Dianne Hackborn:

Android Studio 0.8.10+ представила невероятно полезный инструмент под названием Memory Monitor.

Для чего это хорошо:

  • Отображение доступной и используемой памяти в виде графика и событий сборки мусора с течением времени.
  • Быстрая проверка, может ли медлительность приложения быть связана с чрезмерными событиями сборки мусора.
  • Быстрая проверка, может ли сбой приложения быть связан с нехваткой памяти.

Рисунок 1. Принудительное событие GC (Сборка мусора) на Android Memory Monitor

Используя его, вы можете получить много полезной информации о потреблении оперативной памяти вашего приложения.

1) Наверное, нет, по крайней мере, не с Java.
2)

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

Мы выяснили, что все стандартные способы получения полной памяти текущего процесса имеют некоторые проблемы.

  • Runtime.getRuntime().totalMemory(): возвращает только память JVM
  • ActivityManager.getMemoryInfo(), Process.getFreeMemory() и все остальное на основе /proc/meminfo - возвращает информацию о памяти обо всех объединенных процессах (например, android_util_Process.cpp)
  • Debug.getNativeHeapAllocatedSize() - использует mallinfo() которые возвращают информацию о распределении памяти, выполненной malloc() и только связанные функции (см. android_os_Debug.cpp)
  • Debug.getMemoryInfo() - делает работу, но это слишком медленно. Требуется около 200 мс на Nexus 6 для одного звонка. Снижение производительности делает эту функцию бесполезной для нас, поскольку мы регулярно ее вызываем, и каждый вызов довольно заметен (см. Android_os_Debug.cpp)
  • ActivityManager.getProcessMemoryInfo(int[]) - звонки Debug.getMemoryInfo() внутренне (см. ActivityManagerService.java)

Наконец, мы закончили с использованием следующего кода:

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;

Возвращает метрику VmRSS. Вы можете найти более подробную информацию об этом здесь: один, два и три.


PS Я заметил, что в теме по-прежнему отсутствует фактический и простой фрагмент кода, позволяющий оценить использование частной памяти процессом, если производительность не является критическим требованием:

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;

Я запутался, читая ответы, поэтому решил почитать документацию. Хорошо давай сделаем это:

Использование собственной памяти кучи

Вы можете получить собственный размер памяти кучи устройства с помощью объекта Debug :

      long nativeTotal = Debug.getNativeHeapSize();
long nativeFree = Debug.getNativeHeapFreeSize();
long nativeAllocated = Debug.getNativeHeapAllocatedSize();
long nativeUsed = nativeTotal - nativeFree;

Примечание: nativeUsed а также nativeAllocated имеют одинаковую ценность.

Использование памяти во время выполнения

Использование памяти с использованием объекта времени выполнения приложения. Каждое приложение имеет объект времени выполнения для доступа к его интерфейсу:

      Runtime runtime = Runtime.getRuntime();
long runtimeMax = runtime.maxMemory();
long runtimeTotal = runtime.totalMemory();
long runtimeFree = runtime.freeMemory();
long runtimeUsed = runtimeTotal - runtimeFree;

Использование системной памяти

Вы можете получить информацию об использовании памяти системой с помощью объекта ActivityManager.MemoryInfo :

      ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
long systemTotal = memoryInfo.totalMem;
long systemFree = memoryInfo.availMem;
long systemUsed = systemTotal - systemFree;

Использование памяти Proc

Другой способ узнать использование памяти системой - это файла проанализировать содержимое/ proc / meminfo в Linux.

      RandomAccessFile reader = new RandomAccessFile("/proc/meminfo", "r");
Pattern pattern = Pattern.compile("(\\d+)");
Matcher totalMatcher = pattern.matcher(reader.readLine());
totalMatcher.find();
long procTotal = Long.parseLong(totalMatcher.group(1)) * 1024L; // KB to B
Matcher freeMatcher = pattern.matcher(reader.readLine());
freeMatcher.find();
long procFree = Long.parseLong(freeMatcher.group(1)) * 1024L; // KB to B
long procUsed = procTotal - procFree;

Хорошо, я все еще в замешательстве. Но это были все способы использования памяти. ВСЕ ИХ ЧТОБЫ ОТЛАДИТЬ ПРИЛОЖЕНИЕ !!!

Я также даю ссылку для каждого из них, чтобы прочитать об их использовании и информации.

В Android Studio 3.0 они представили Android-Profiler, чтобы помочь вам понять, как ваше приложение использует ресурсы процессора, памяти, сети и батареи.

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

Выше приведено много ответов, которые определенно помогут вам, но (после 2 дней использования и изучения инструментов памяти adb) я думаю, что тоже могу помочь с моим мнением.

Как говорит Хэкбод: Таким образом, если бы вы взяли всю физическую RAM, фактически сопоставленную с каждым процессом, и суммировали все процессы, вы, вероятно, в конечном итоге получили бы число, намного превышающее фактическую общую RAM. таким образом, вы не сможете получить точное количество памяти на процесс.

Но вы можете приблизиться к этому по некоторой логике.. и я расскажу, как..

Есть некоторые API, такие как android.os.Debug.MemoryInfo а также ActivityManager.getMemoryInfo() упомянутое выше, о котором вы, возможно, уже читали и использовали, но я расскажу о другом

Итак, во-первых, вам нужно быть пользователем root, чтобы все заработало. Войдите в консоль с привилегиями root, выполнив su в процессе и получить его output and input stream, Тогда пройдите id\n (введите) в ouputstream и запишите его для обработки выходных данных, если получите входной поток, содержащий uid=0 Вы являетесь пользователем root.

Теперь вот логика, которую вы будете использовать в процессе выше

Когда вы получите выходной поток процесса, передайте команду (procrank, dumpsys meminfo и т. Д.) С \n вместо идентификатора и получить его inputstream и читать, хранить поток в байтах [ ],char[ ] и т.д.. использовать сырые данные.. и все готово!!!!!

разрешение:

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

Проверьте, являетесь ли вы пользователем 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
   } 
}

Выполните вашу команду с 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: результат

Вы получаете необработанные данные в виде единственной строки из консоли, а не в каком-то случае из любого API, который сложно хранить, так как вам придется отделить его вручную.

Это всего лишь попытка, пожалуйста, предложите мне, если я что-то пропустил

Другие вопросы по тегам