Wie kann ich den in meiner Android-Anwendung verwendeten Speicher programmgesteuert finden?
Ich hoffe, es gibt einen Weg, dies zu tun. Und wie bekomme ich auch den freien Speicher des Telefons?
Wie kann ich den in meiner Android-Anwendung verwendeten Speicher programmgesteuert finden?
Ich hoffe, es gibt einen Weg, dies zu tun. Und wie bekomme ich auch den freien Speicher des Telefons?
Antworten:
Beachten Sie, dass die Speichernutzung unter modernen Betriebssystemen wie Linux ein äußerst komplizierter und schwer verständlicher Bereich ist. Tatsächlich ist die Wahrscheinlichkeit, dass Sie die Zahlen, die Sie erhalten, tatsächlich richtig interpretieren, äußerst gering. (Fast jedes Mal, wenn ich mit anderen Ingenieuren die Speichernutzungszahlen betrachte, gibt es eine lange Diskussion darüber, was sie tatsächlich bedeuten, was nur zu einer vagen Schlussfolgerung führt.)
Hinweis: Wir haben jetzt eine viel umfangreichere Dokumentation zum Verwalten des Speichers Ihrer App, die einen Großteil des Materials hier abdeckt und mit dem Status von Android auf dem neuesten Stand ist.
Als erstes sollten Sie wahrscheinlich den letzten Teil dieses Artikels lesen, in dem erläutert wird, wie der Speicher unter Android verwaltet wird:
Änderungen der Service-API ab Android 2.0
Jetzt ActivityManager.getMemoryInfo()
ist unsere API auf höchster Ebene für die Betrachtung der gesamten Speichernutzung. Dies dient hauptsächlich dazu, einer Anwendung zu helfen, zu beurteilen, wie nahe das System daran ist, keinen Speicher mehr für Hintergrundprozesse zu haben, und daher damit beginnen muss, benötigte Prozesse wie Dienste zu beenden. Für reine Java-Anwendungen sollte dies wenig nützlich sein, da das Java-Heap-Limit teilweise dazu dient, zu verhindern, dass eine App das System bis zu diesem Punkt belasten kann.
Auf einer niedrigeren Ebene können Sie die Debug-API verwenden, um Informationen auf Kernel-Ebene zur Speichernutzung abzurufen : android.os.Debug.MemoryInfo
Beachten Sie, dass es ab 2.0 auch eine API gibt ActivityManager.getProcessMemoryInfo
, um diese Informationen zu einem anderen Prozess abzurufen : ActivityManager.getProcessMemoryInfo (int [])
Dies gibt eine Low-Level-MemoryInfo-Struktur mit all diesen Daten zurück:
/** 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;
Aber, was ist der Unterschied zwischen Pss
, PrivateDirty
und SharedDirty
... na ja jetzt beginnt der Spaß.
Viel Speicher in Android (und Linux-Systemen im Allgemeinen) wird tatsächlich von mehreren Prozessen gemeinsam genutzt. Wie viel Speicher ein Prozess verwendet, ist also nicht klar. Fügen Sie zusätzlich dieses Paging auf die Festplatte hinzu (geschweige denn Swap, das wir unter Android nicht verwenden), und es ist noch weniger klar.
Wenn Sie also den gesamten physischen RAM verwenden würden, der tatsächlich jedem Prozess zugeordnet ist, und alle Prozesse addieren würden, würden Sie wahrscheinlich eine Zahl erhalten, die viel größer ist als der tatsächliche Gesamtspeicher.
Die Pss
Anzahl ist eine vom Kernel berechnete Metrik, die die Speicherfreigabe berücksichtigt. Grundsätzlich wird jede Seite des Arbeitsspeichers in einem Prozess durch ein Verhältnis der Anzahl anderer Prozesse skaliert, die diese Seite ebenfalls verwenden. Auf diese Weise können Sie (theoretisch) das PSS über alle Prozesse hinweg addieren, um den von ihnen verwendeten Gesamtspeicher zu sehen, und das PSS zwischen Prozessen vergleichen, um eine grobe Vorstellung von ihrem relativen Gewicht zu erhalten.
Die andere interessante Metrik hier ist PrivateDirty
im Grunde die Menge an RAM innerhalb des Prozesses, die nicht auf die Festplatte ausgelagert werden kann (sie wird nicht von denselben Daten auf der Festplatte unterstützt) und nicht mit anderen Prozessen gemeinsam genutzt wird. Eine andere Möglichkeit, dies zu betrachten, ist der RAM, der dem System zur Verfügung steht, wenn dieser Prozess unterbrochen wird (und wahrscheinlich schnell in Caches und andere Verwendungen davon zusammengefasst wird).
Das sind so ziemlich die SDK-APIs dafür. Sie können als Entwickler jedoch noch mehr mit Ihrem Gerät tun.
Mit adb
können Sie viele Informationen über die Speichernutzung eines laufenden Systems abrufen. Ein häufiger Befehl ist der Befehl, adb shell dumpsys meminfo
der eine Reihe von Informationen über die Speichernutzung jedes Java-Prozesses ausspuckt, die die oben genannten Informationen sowie eine Vielzahl anderer Dinge enthalten. Sie können auch den Namen oder die PID eines einzelnen Prozesses eingeben, um beispielsweise adb shell dumpsys meminfo system
den Systemprozess zu sehen:
** MEMINFO in pid 890 [System] ** native dalvik andere total Größe: 10940 7047 N / A 17987 zugewiesen: 8943 5516 N / A 14459 frei: 336 1531 N / A 1867 (Pss): 4585 9282 11916 25783 (gemeinsam schmutzig geteilt): 2184 3596 916 6696 (priv schmutzig): 4504 5956 7456 17916 Objekte Aufrufe: 149 ViewRoots: 4 AppContexts: 13 Aktivitäten: 0 Vermögenswerte: 4 AssetManager: 4 Lokale Ordner: 141 Proxy-Ordner: 158 Todesempfänger: 49 OpenSSL-Sockets: 0 SQL Heap: 205 dbDateien: 0 numPagers: 0 inactivePageKB: 0 activePageKB: 0
Der obere Abschnitt ist die wichtigste, wo size
ist die Gesamtgröße in Adressraum eines bestimmten Haufens, allocated
ist die kb Istverrechnungen dass Haufen denken , es hat, free
ist der restliche kb des Heap frei für zusätzliche Zuteilungen hat, und pss
und priv dirty
ist die gleiche wie zuvor speziell für Seiten beschrieben, die jedem der Heaps zugeordnet sind.
Wenn Sie nur die Speichernutzung über alle Prozesse hinweg betrachten möchten, können Sie den Befehl verwenden adb shell procrank
. Die Ausgabe auf demselben System sieht folgendermaßen aus:
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 Zygote 977 24100K 24096K 5667K 4340K android.process.acore ... 59 336K 332K 99K 92K / system / bin / installd 60 396K 392K 93K 84K / System / Bin / Keystore 51 280K 276K 74K 68K / System / Behälter / Servicemanager 54 256K 252K 69K 64K / system / bin / debuggerd
Hier sind die Spalten Vss
und Rss
im Grunde genommen Rauschen (dies sind der einfache Adressraum und die RAM-Auslastung eines Prozesses. Wenn Sie die RAM-Auslastung prozessübergreifend addieren, erhalten Sie eine lächerlich große Zahl).
Pss
ist wie wir vorher gesehen haben und Uss
ist Priv Dirty
.
Interessantes zu beachten: Pss
und unterscheiden Uss
sich geringfügig (oder mehr als geringfügig) von dem, was wir gesehen haben meminfo
. Warum ist das so? Nun, Procrank verwendet einen anderen Kernel-Mechanismus, um seine Daten zu sammeln als dies der meminfo
Fall ist, und sie liefern leicht unterschiedliche Ergebnisse. Warum ist das so? Ehrlich gesagt habe ich keine Ahnung. Ich glaube, es procrank
mag genauer sein ... aber wirklich, das lässt nur den Punkt: "Nehmen Sie alle Erinnerungsinformationen, die Sie mit einem Salzkorn erhalten; oft ein sehr großes Korn."
Schließlich gibt es den Befehl adb shell cat /proc/meminfo
, der eine Zusammenfassung der gesamten Speichernutzung des Systems gibt. Hier gibt es viele Daten, nur die ersten Zahlen, die es wert sind, diskutiert zu werden (und die übrigen, die von wenigen Menschen verstanden werden, und meine Fragen an diese wenigen Menschen, die oft zu widersprüchlichen Erklärungen führen):
MemTotal: 395144 kB MemFree: 184936 kB Puffer: 880 kB Zwischengespeichert: 84104 kB SwapCached: 0 kB
MemTotal
ist die Gesamtmenge an Speicher, die dem Kernel und dem Benutzerbereich zur Verfügung steht (häufig weniger als der tatsächliche physische RAM des Geräts, da ein Teil dieses RAM für das Radio, DMA-Puffer usw. benötigt wird).
MemFree
ist die Menge an RAM, die überhaupt nicht verwendet wird. Die Zahl, die Sie hier sehen, ist sehr hoch; Auf einem Android-System sind dies normalerweise nur wenige MB, da wir versuchen, den verfügbaren Speicher zu verwenden, um die Prozesse am Laufen zu halten
Cached
ist der RAM, der für Dateisystem-Caches und andere solche Dinge verwendet wird. Typische Systeme benötigen ungefähr 20 MB, um nicht in einen schlechten Paging-Zustand zu geraten. Der Android Out-of-Memory-Killer ist auf ein bestimmtes System abgestimmt, um sicherzustellen, dass Hintergrundprozesse beendet werden, bevor der zwischengespeicherte RAM von ihnen zu stark beansprucht wird, um zu einem solchen Paging zu führen.
Ja, Sie können Speicherinformationen programmgesteuert abrufen und entscheiden, ob Sie speicherintensive Arbeiten ausführen möchten.
Holen Sie sich die VM-Heap-Größe, indem Sie Folgendes aufrufen:
Runtime.getRuntime().totalMemory();
Holen Sie sich den zugewiesenen VM-Speicher, indem Sie Folgendes aufrufen:
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
Holen Sie sich das VM Heap Size Limit, indem Sie Folgendes aufrufen:
Runtime.getRuntime().maxMemory()
Holen Sie sich den nativ zugewiesenen Speicher, indem Sie Folgendes aufrufen:
Debug.getNativeHeapAllocatedSize();
Ich habe eine App erstellt, um das Verhalten von OutOfMemoryError herauszufinden und die Speichernutzung zu überwachen.
https://play.google.com/store/apps/details?id=net.coocood.oomresearch
Den Quellcode erhalten Sie unter https://github.com/coocood/oom-research
Dies ist eine laufende Arbeit, aber das verstehe ich nicht:
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");
}
}
Warum wird die PID nicht dem Ergebnis in activityManager.getProcessMemoryInfo () zugeordnet? Natürlich möchten Sie die resultierenden Daten aussagekräftig machen. Warum hat Google es so schwierig gemacht, die Ergebnisse zu korrelieren? Das aktuelle System funktioniert nicht einmal gut, wenn ich die gesamte Speichernutzung verarbeiten möchte, da das zurückgegebene Ergebnis ein Array von android.os.Debug.MemoryInfo-Objekten ist, aber keines dieser Objekte sagt Ihnen tatsächlich, welchen Pids sie zugeordnet sind. Wenn Sie einfach ein Array aller Pids übergeben, können Sie die Ergebnisse nicht verstehen. Soweit ich weiß, ist es sinnlos, mehr als eine PID gleichzeitig zu übergeben. Wenn dies der Fall ist, warum sollte es dann so gemacht werden, dass activityManager.getProcessMemoryInfo () nur ein int-Array akzeptiert?
Hackbod's ist eine der besten Antworten auf Stack Overflow. Es wirft ein Licht auf ein sehr dunkles Thema. Es hat mir sehr geholfen.
Eine weitere wirklich hilfreiche Ressource ist dieses Video, das man gesehen haben muss: Google I / O 2011: Speicherverwaltung für Android Apps
AKTUALISIEREN:
Process Stats, ein Dienst, mit dem Sie herausfinden können , wie Ihre App den Speicher verwaltet, wird im Blogbeitrag erläutert. Process Stats: Verstehen, wie Ihre App RAM verwendet von Dianne Hackborn:
Android Studio 0.8.10+ hat ein unglaublich nützliches Tool namens Memory Monitor eingeführt .
Wofür es gut ist:
- Anzeigen des verfügbaren und verwendeten Speichers in einem Diagramm sowie von Speicherbereinigungsereignissen im Laufe der Zeit.
- Schnelles Testen, ob die Langsamkeit der App mit übermäßigen Speicherbereinigungsereignissen zusammenhängt.
- Schnelles Testen, ob App-Abstürze möglicherweise auf Speichermangel zurückzuführen sind.
Abbildung 1. Erzwingen eines GC-Ereignisses (Garbage Collection) auf dem Android Memory Monitor
Mithilfe dieser Funktion können Sie in Echtzeit zahlreiche gute Informationen zum RAM-Verbrauch Ihrer App erhalten.
1) Ich denke nicht, zumindest nicht von Java.
2)
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
MemoryInfo mi = new MemoryInfo();
activityManager.getMemoryInfo(mi);
Log.i("memory free", "" + mi.availMem);
Wir haben herausgefunden, dass alle Standardmethoden zum Abrufen des Gesamtspeichers des aktuellen Prozesses einige Probleme aufweisen.
Runtime.getRuntime().totalMemory()
: Gibt nur JVM-Speicher zurückActivityManager.getMemoryInfo()
, Process.getFreeMemory()
Und alles , was basierend auf /proc/meminfo
- Renditen Speicher Informationen über alle Prozesse kombiniert (zB android_util_Process.cpp )Debug.getNativeHeapAllocatedSize()
- Verwendet, mallinfo()
die nur Informationen über Speicherzuweisungen zurückgeben, die von malloc()
und verwandten Funktionen ausgeführt werden (siehe android_os_Debug.cpp ).Debug.getMemoryInfo()
- macht den Job, aber es ist zu langsam. Auf dem Nexus 6 dauert ein einzelner Anruf etwa 200 ms . Der Leistungsaufwand macht diese Funktion für uns nutzlos, da wir sie regelmäßig aufrufen und jeder Aufruf ziemlich auffällig ist (siehe android_os_Debug.cpp ).ActivityManager.getProcessMemoryInfo(int[])
- ruft Debug.getMemoryInfo()
intern auf (siehe ActivityManagerService.java )Schließlich haben wir den folgenden Code verwendet:
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;
Es gibt die VmRSS- Metrik zurück. Weitere Details dazu finden Sie hier: eins , zwei und drei .
PS Ich habe festgestellt, dass dem Thema immer noch ein tatsächlicher und einfacher Codeausschnitt fehlt, wie die private Speichernutzung des Prozesses geschätzt werden kann, wenn die Leistung keine kritische Anforderung ist:
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;
In Android Studio 3.0 haben sie den Android-Profiler eingeführt, um Ihnen zu helfen, zu verstehen, wie Ihre App CPU-, Speicher-, Netzwerk- und Batterieressourcen verwendet.
https://developer.android.com/studio/profile/android-profiler
Es gibt viele Antworten oben, die Ihnen definitiv helfen werden, aber (nach 2 Tagen des Erschwingens und Nachforschens über ADB-Speicher-Tools) denke ich, dass ich auch mit meiner Meinung helfen kann .
Wie Hackbod sagt: Wenn Sie also den gesamten physischen RAM verwenden würden, der tatsächlich jedem Prozess zugeordnet ist, und alle Prozesse addieren würden, würden Sie wahrscheinlich eine Zahl erhalten, die viel größer ist als der tatsächliche Gesamtspeicher. Es gibt also keine Möglichkeit, die genaue Speichermenge pro Prozess abzurufen.
Aber Sie können sich dem durch eine Logik nähern ... und ich werde sagen, wie ...
Es gibt einige APIs wie
android.os.Debug.MemoryInfo
undActivityManager.getMemoryInfo()
oben erwähnt, über die Sie möglicherweise bereits gelesen und verwendet wurden, aber ich werde auf andere Weise darüber sprechen
Zunächst müssen Sie also ein Root-Benutzer sein, damit es funktioniert. Holen Sie sich in die Konsole mit Root-Rechten, indem Sie su
in Prozess ausführen und seine erhalten output and input stream
. Dann passieren id\n
(Enter) in ouputstream und schreiben Sie es auf Prozessausgang wird Wenn bekommen einen Inputstream enthält uid=0
, sind Sie Root - Benutzer.
Hier ist die Logik, die Sie im obigen Prozess verwenden werden
Wenn Sie ouputstream des Prozessdurchlaufs erhalten, befehlen Sie (procrank, dumpsys meminfo etc ...) \n
anstelle von id und erhalten Sie seine inputstream
und lesen Sie, speichern Sie den Stream in Bytes [], char [] etc .. verwenden Sie Rohdaten..und Sie sind fertig!!!!!
Genehmigung :
<uses-permission android:name="android.permission.FACTORY_TEST"/>
Überprüfen Sie, ob Sie Root-Benutzer sind:
// 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
}
}
Führen Sie Ihren Befehl mit aus 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();
}
}
Sie erhalten Rohdaten in einer einzelnen Zeichenfolge von der Konsole anstatt in einigen Fällen von einer API, deren Speicherung komplex ist, da Sie sie manuell trennen müssen .
Dies ist nur ein Versuch, bitte schlagen Sie mir vor, wenn ich etwas verpasst habe