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


800

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

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


2
Или, если кто-то хочет узнать больше обо всем этом, пожалуйста, обратитесь http://elinux.org/Android_Memory_Usage

1
Управление памятью вашего приложения developer.android.com/topic/performance/memory
Shomu

Ответы:


1008

Обратите внимание, что использование памяти в современных операционных системах, таких как 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), и это еще менее понятно.

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

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

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

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

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

** MEMINFO в pid 890 [система] **
                    родной далвик другой итого
            размер: 10940 7047 N / A 17987
       выделено: 8943 5516 н / д 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 Актив Менеджеры: 4
   Местные связующие: 141 прокси Связующие: 158
Получатели смерти: 49
 Разъемы OpenSSL: 0

 SQL
            куча: 205 дБфайлов: 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 настроен для конкретной системы, чтобы гарантировать, что фоновые процессы будут убиты до того, как кэшированная оперативная память израсходует ими слишком много, чтобы привести к такой подкачке.


1
Посмотрите на pixelbeat.org/scripts/ps_mem.py, который использует методы, упомянутые выше, чтобы показать используемую оперативную память для программ
pixelbeat

17
Очень красиво написано! Я написал пост об управлении памятью и использовании различных инструментов для проверки вашего использования кучи здесь macgyverdev.blogspot.com/2011/11/… если кто-нибудь найдет это полезным.
Йохан Норен

3
Какие именно две колонки "далвик" являются "родными"?
Dacongy

1
Что именно "родной", "далвик", "другой"? В моем приложении «другое» очень огромное? как я могу уменьшить его?
Landry

Я могу использовать "adb shell dumpsys meminfo", но "adb shell procrank" скажи мне "/ system / bin / sh: procrank: not found". У меня нет подсказки. Хочу помочь мне.
Хьюго

79

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

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

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


7
Эта среда выполнения вернет использование памяти текущим процессом или общей системной кучей?
Махендран

1
@mahemadhi из JavaDoc метода totalMemory () «Возвращает общий объем памяти, доступной для работающей программы»
Алекс,

Это не правильный ответ на вопрос. Ответ не о конкретном приложении.
Амир Резазаде

52

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

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?


2
Скорее всего, они в том же порядке, что и входной массив.
Таер

2
Это кажется очень не интуитивным способом делать вещи. Да, это, вероятно, так, но как это в любом случае ООП?
Райан Бизли

6
API был разработан для эффективности, а не простоты использования или простоты. Это не то, что 99% приложений должны когда-либо касаться, поэтому эффективность является наиболее важной целью дизайна.
hackbod

2
Справедливо. Я пытаюсь написать внутренний инструмент для отслеживания использования памяти для одного или нескольких приложений, которые мы пишем. В результате я ищу способ сделать этот мониторинг, в то же время оказывая минимальное влияние на другие процессы, но все же быть как можно более подробным с результатами (постобработка). Итерации по процессам и последующие вызовы для каждого процесса кажутся неэффективными, если предположить, что для каждого вызова .getProcessMemoryInfo есть некоторые издержки. Если гарантируется, что возвращаемый массив будет в том же порядке, что и вызов, я обработаю результаты вслепую и просто приму четность.
Райан Бизли

5
Это небольшая проблема, но для журнала нет необходимости добавлять перевод строки, который обрабатывается для вас.
ThomasW

24

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

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


ОБНОВИТЬ:

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


19

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

введите описание изображения здесь

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

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

введите описание изображения здесь

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

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


16

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

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

1
замените на (ActivityManager activityManager = (ActivityManager) getSystemService (ACTIVITY_SERVICE);) вместо (ActivityManager activityManager = = (ActivityManager) getSystemService (ACTIVITY_SERVICE);)
Раджкамал,

7

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

  • 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;


1

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

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

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

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

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

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

Когда вы получаете ouputstream процесса, передайте свою команду (procrank, dumpsys meminfo и т. Д.) С \nвместо id и получите его 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, который сложно хранить, так как вам придется отделить его вручную .

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.