Як виявити використання пам'яті свого додатка в Android?


799

Як я можу програмно знайти пам’ять, яка використовується в моєму додатку Android?

Я сподіваюся, що є спосіб це зробити. Плюс, як мені також отримати вільну пам'ять телефону?


2
Або якщо хтось хоче дізнатися більше про все це, зверніться до http://elinux.org/Android_Memory_Usage

Відповіді:


1007

Зауважте, що використання пам'яті в сучасних операційних системах, таких як 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, в основному, об'єм оперативної пам’яті всередині процесу, який не можна підключити до диска (він не підтримується тими ж даними на диску) і не поділяється ні з якими іншими процесами. Ще один спосіб подивитися на це - оперативна пам’ять, яка стане доступною для системи, коли цей процес піде (і, ймовірно, швидко підпадає під кеші та інші його використання).

Це майже API SDK для цього. Однак ви можете зробити більше, як розробник свого пристрою.

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

** MEMINFO в pid 890 [система] **
                    рідний далвік інші загальні
            розмір: 10940 7047 н / б 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 Переглядів: 4
     AppContexts: 13 Діяльність: 0
          Активи: 4 Менеджери активів: 4
   Місцеві в'яжучі: 141 проксі-палітур: 158
Одержувачі смерті: 49
 Розетки OpenSSL: 0

 SQL
            купа: 205 dbFiles: 0
       numPagers: 0 InactivePageKB: 0
    activePageKB: 0

Верхній розділ є основним, де sizeзагальний розмір в адресному просторі певної купи, allocatedце kb фактичних виділень, які купує, як це має, чи freeє решта kb вільною, яку має купа для додаткових розподілів, 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 / система / бін / встановлення
   60 396K 392K 93K 84K / система / бін / брелок
   51 280K 276K 74K 68K / система / бін / сервісний менеджер
   54 256K 252K 69K 64K / система / бін / налагоджувач

Тут Vssі Rssстовпці - це в основному шум (це прямий адресний простір та використання оперативної пам’яті в процесі, де, якщо додати використання оперативної пам’яті в процесах, ви отримаєте смішно велику кількість).

Pssє, як ми бачили раніше, і Ussє Priv Dirty.

Цікаву річ тут слід зазначити: Pssі Ussтрохи (або більш ніж трохи) відрізняються від того, що ми бачили в meminfo. Чому так? Well prorank використовує інший механізм ядра для збору своїх даних, ніж meminfoце робить, і вони дають дещо інші результати. Чому так? Чесно кажучи, у мене немає поняття. Я вважаю, що procrankможе бути більш точним ... але насправді це просто залишає крапку: "візьміть будь-яку інформацію про пам'ять із зерном солі; часто дуже великим зерном".

Нарешті, є команда, adb shell cat /proc/meminfoяка дає підсумок загального використання пам'яті системи. Даних тут багато, лише перші кілька номерів, які варто обговорити (а решта їх розуміє мало хто, і мої запитання тих небагатьох людей про них часто призводять до суперечливих пояснень):

MemTotal: 395144 кБ
MemFree: 184936 кБ
Буфери: 880 кБ
Кеш: 84104 кБ
Зміна: 0 кБ

MemTotal - загальний об'єм пам'яті, доступний для ядра та користувальницького простору (часто менше фактичної фізичної оперативної пам’яті пристрою, оскільки деяка частина цієї оперативної пам’яті потрібна для радіо, DMA-буферів тощо).

MemFree- це кількість оперативної пам’яті, яка взагалі не використовується. Кількість, яку ви бачите тут, дуже велика; як правило, в системі Android це буде лише кілька Мб, оскільки ми намагаємось використовувати доступну пам’ять, щоб тримати процеси

Cachedє оперативна пам'ять, яка використовується для кешів файлової системи та інших подібних речей. Для цього типовим системам потрібно мати 20 Мб або близько того, щоб уникнути потрапляння в поганий стан підкачки; Android-вбивця пам'яті налаштований на конкретну систему, щоб переконатися, що фонові процеси вбиваються, перш ніж кешована оперативна пам’ять буде витрачена ними занадто багато, що призведе до такого підкачки.


1
Подивіться на pixelbeat.org/scripts/ps_mem.py, який використовує методи, згадані вище, для показу використовуваної оперативної пам’яті для програм
pixelbeat

17
Дуже приємно написано! Я написав пост про управління пам’яттю та використання різних інструментів для перевірки використання вашої купи тут macgyverdev.blogspot.com/2011/11/…, якщо хтось вважає це корисним.
Йохан Норен

3
Які саме дві колонки "дальвік" є "рідною"?
dacongy

1
Які саме "рідні" "дальвіки" "інші"? У моєму додатку "інші" дуже величезні? як я можу зменшити його?
ландшафт

Я можу використовувати «ADB оболонки dumpsys MemInfo», але «ADB оболонки ProcRank» скажи мені .. "/ Система / бен / ш: ProcRank: не знайшли" у мене немає clue.Wish ви можете мені допомогти
Hugo

79

Так, ви можете отримувати інформацію про пам'ять програмно і вирішувати, чи потрібно робити об'ємну пам'ять.

Отримайте розмір купи VM, зателефонувавши:

Runtime.getRuntime().totalMemory();

Отримайте розподілену пам'ять VM, зателефонувавши:

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

Отримайте ліміт розміру VM Heap за телефоном:

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 of 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, але жоден із цих об'єктів насправді не повідомляє вам, з якими прив’язками вони пов’язані. Якщо ви просто перейдете в масив усіх підказок, ви не зможете зрозуміти результати. Як я розумію, це використання, безглуздо переходити за одним підом одночасно, і якщо це так, навіщо робити це так, що ActivityManager.getProcessMemoryInfo () приймає лише масив int?


2
Вони, ймовірно, в тому ж порядку, що і вхідний масив.
taer

2
Це здається дуже неінтуїтивним способом робити речі. Так, це, мабуть, так, але як це в будь-якому випадку OOP?
Райан Біслі

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

2
Справедливо. Я намагаюся написати внутрішній інструмент для відстеження використання пам'яті для однієї або кількох програм, про які ми пишемо. Як результат, я шукаю спосіб зробити цей моніторинг, впливаючи на інші процеси найменше, але все ще буду максимально деталізованими з можливими результатами (після обробки). Ітерація над процесами, а потім здійснення дзвінків для кожного процесу, здається, неефективні, якщо припустити, що для кожного виклику .getProcessMemoryInfo є деякий накладні витрати. Якщо гарантовано повернутий масив буде в тому ж порядку, що і виклик, я опрацюю результати наосліп і просто прийму парність.
Райан Бізлі

5
Це незначна проблема, але для журналу не потрібно додавати стрічковий канал, який обробляється для вас.
ThomasW

24

Hackbod's - одна з найкращих відповідей на переповнення стека. Він кидає світло на дуже незрозумілу тему. Це мені дуже допомогло.

Ще один дійсно корисний ресурс - це обов'язкове для перегляду відео: Google I / O 2011: Управління пам’яттю для Android Apps


ОНОВЛЕННЯ:

Статистика процесів - сервіс для виявлення того, як ваш додаток управляє пам’яттю, пояснюється у публікації блогу Статистика процесу: розуміння того, як ваш додаток використовує оперативну пам’ять від Діанна Хакборн:


19

Android Studio 0.8.10+ випустив неймовірно корисний інструмент під назвою Monitor Monitor .

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

Для чого це добре:

  • Відображення наявної та використаної пам’яті у графіку та події з вивезення сміття з часом.
  • Швидке тестування, чи може повільність програми пов’язана з надмірними подіями збору сміття.
  • Швидке тестування, чи можуть збої в додатку пов’язані із втратою пам'яті.

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

Малюнок 1. Вимушення події GC (збирання сміття) на моніторі пам'яті Android

Ви можете отримати багато хорошої інформації про споживання оперативної пам’яті в режимі реального часу, використовуючи її.


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);)
Rajkamal

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) я думаю, що я можу допомогти і з моєю думкою .

Як говорить Хакбод: Таким чином, якби ви взяли всю фізичну ОЗУ, фактично відображену в кожному процесі, і склали всі процеси, ви, ймовірно, отримали б число, набагато більше, ніж фактична загальна ОЗУ. тому немає можливості отримати точний об'єм пам'яті за один процес.

Але ти можеш наблизитися до неї за деякою логікою .. і я розповім як ..

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

Отже, спочатку вам потрібно бути користувачем root, щоб змусити його працювати. Увійдіть в консоль з привілеєм root, виконавши suв процесі і отримайте його output and input stream. Потім перейдіть id\n (введіть) у вихідний потік і напишіть його для обробки результату. Якщо ви отримаєте вхідний потік, що містить uid=0, ви користуєтеся кореневим користувачем.

Тепер ось логіка, яку ви будете використовувати у вищезгаданому процесі

Коли ви отримуєте ouputstream процесу передати накажеш (ProcRank, dumpsys MemInfo і т.д ...) з \nзамість ідентифікатора і отримати його inputstreamі прочитати, зберегти потік в байтах [], символ [] і т.д .. використовувати необроблений data..and ти по зробили !!!!!

дозвіл:

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

Перевірте, чи ви користуєтеся кореневим користувачем:

// 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.