Wenn Sie ein Objekt im Code Ihrer App freigeben oder einen temporären Stream schließen, wird der physische RAM nicht immer an das Betriebssystem zurückgegeben. Benutzerdefinierte native Allocators, Garbage-Collected-Laufzeiten und Hash-Tabellen mit offener Adressierung behalten häufig virtuelle Speicherbereiche bei, die noch lange nach dem Verschwinden der logischen Daten darin von physischen Seiten unterstützt werden. Auch das Lesen oder Memory-Mapping großer Asset-Archive füllt den Kernel-Seiten-Cache, der weiterhin der Speicher-Kontrollgruppe (memcg) Ihrer App zugerechnet wird.
Auf dieser Seite wird erläutert, wie Sie mit den Systemaufrufen madvise und posix_fadvise nicht verwendete anonyme und dateibasierte Seiten an das Betriebssystem zurückgeben, während virtuelle Adressreservierungen und Dateideskriptoren intakt bleiben. Außerdem wird beschrieben, wie Sie die Auswirkungen in adb shell und Perfetto prüfen.
Warum bei der logischen Freigabe physischer RAM beibehalten wird
Der Linux-Kernel verfolgt den physischen Arbeitsspeicher auf der Ebene von Hardwareseiten, die auf Android-Geräten 4 KB oder 16 KB groß sind. Wenn ein Prozess einen Bereich des virtuellen Adressraums mit mmap reserviert, erstellt der Kernel einen virtuellen Speicherbereich (VMA), ohne physischen RAM zuzuweisen. Der Kernel weist eine physische Seite erst dann zu und berechnet sie, wenn der Prozess diese Seite zum ersten Mal berührt. Bei anonymem Speicher wird beim ersten Lesen eine freigegebene Nullseite zugeordnet, sodass nur beim Schreiben RAM zugewiesen wird.
Sobald eine Seite in den RAM geladen wurde, weiß der Kernel nicht, ob die Byte auf dieser Seite noch Live-Objekte in der Laufzeit Ihrer App darstellen. Zwei gängige Muster sorgen dafür, dass physische Seiten im Speicher bleiben, nachdem sie von Ihrer App nicht mehr verwendet werden:
- Anonyme Arenen und Pools: Benutzerdefinierte native Allocators (z. B. Slab-, Pool- oder Frame-Arena-Allocators), verwaltete Laufzeiten mit nicht verschiebbaren Heap-Bereichen und große Hash-Tabellen reservieren zusammenhängende virtuelle Adressbereiche mit
mmapunter Verwendung vonMAP_PRIVATE | MAP_ANONYMOUS. Wenn Ihre Anwendung eine Arena zurücksetzt, einen Block an einen benutzerdefinierten Pool zurückgibt oder eine Hash-Tabelle durch Nullsetzen der Metadaten löscht, bleiben die zugrunde liegenden Seiten „dirty“. Ohne einen expliziten Hinweis an den Kernel werden diese Seiten weiterhin auf die anonyme Resident Set Size (RSS) und die Proportional Set Size (PSS) Ihres Prozesses und seinememory.current-cgroup-Gebühr angerechnet. Bei Speicherdruck komprimiert der Kernel sie in ZRAM, wo sie weiterhin Auslagerungsspeicher belegen, dermemory.swap.currentzugerechnet wird. - Dateibasierter Seitencache aus dem Asset-Streaming: Wenn Ihre App große Asset-Bundles, Level-Archive, Mediencontainer oder Modelle für maschinelles Lernen mit
read,preadoder dateibasiertemmmapliest, speichert der Kernel jede gelesene Seite im Seitencache und rechnet diese Seiten demfile-Zähler in dermemory.stat-Datei der cgroup Ihres Prozesses an. Wenn deine App dieses Asset dekomprimiert oder in einen anonymen Laufzeitpuffer kopiert oder auf die GPU hochlädt, wird das Asset im RAM dupliziert, wenn die Rohdateiseiten im Seitencache verbleiben.
Anonyme Seiten veröffentlichen
Der Systemaufruf madvise gibt dem Kernel Hinweise dazu, wie Ihr Prozess einen virtuellen Adressbereich voraussichtlich verwenden wird. Bei privaten anonymen Zuordnungen (MAP_PRIVATE |
MAP_ANONYMOUS) wird der Kernel durch die Übergabe von MADV_DONTNEED angewiesen, die physischen Seiten und alle ausgelagerten ZRAM-Einträge, die den angegebenen seitenbündigen Bereich unterstützen, sofort zu verwerfen.
Nach madvise mit MADV_DONTNEED wird Folgendes zurückgegeben:
- Der virtuelle Adressbereich bleibt im Adressbereich Ihres Prozesses reserviert und gültig (
VmSizeändert sich nicht). Zeiger und Arena-Grenzen bleiben also intakt, ohne dassmunmapodermmapaufgerufen werden muss. - Die Zuordnung der physischen Seiten wird sofort aufgehoben und sie werden freigegeben. Dadurch werden
RssAnon,Pss, der Zähleranoninmemory.statundmemory.swap.currentfür alle Seiten, die sich im ZRAM befanden, reduziert. - Bei jedem nachfolgenden Lese- oder Schreibvorgang an einer Adresse in diesem Bereich wird bei Bedarf eine neue, mit Nullen gefüllte Seite erstellt.
| Advice-Flag | Zielzuordnung | Kernelverhalten unter Android |
|---|---|---|
MADV_DONTNEED |
Privat, anonym (MAP_PRIVATE | MAP_ANONYMOUS) |
Physische Seiten und ZRAM-Swap-Slots werden sofort verworfen. Bei nachfolgenden Zugriffen werden mit Nullen gefüllte Seiten fehlerhaft. Deterministischer Rückgang bei RssAnon und memory.current. |
MADV_DONTNEED |
Dateibasiert (MAP_PRIVATE oder MAP_SHARED) |
Hebt die Zuordnung von Seiten aus den Prozessseitentabellen auf (RssFile-Drops). Private Copy-on-Write-Seiten in einer MAP_PRIVATE-Zuordnung werden verworfen, sodass Änderungen verloren gehen und die Datei beim nächsten Zugriff noch einmal gelesen wird. Geänderte MAP_SHARED-Seiten werden später wieder in den Speicher geschrieben. Bereinigte Seiten bleiben im Seitencache, bis sie entfernt werden. |
MADV_FREE |
Privat, anonym (MAP_PRIVATE | MAP_ANONYMOUS) |
Markiert Seiten als verzögert wiederverwendbar. Der Kernel behält die Seiten in RssAnon bei, wenn kein Speicherdruck besteht, und gibt sie nur frei, wenn die globale oder memcg-Freigabe ausgeführt wird. |
MADV_WILLNEED |
Dateigestützt oder ausgelagert anonym | Startet asynchrones Readahead für dateibasierte Zuordnungen oder liest ausgelagerte anonyme Seiten vorab aus ZRAM in den Swap-Cache zurück. |
Interne Bereiche an der Seitengröße der Laufzeit ausrichten
madvise wird auf ganze Seiten angewendet. Dafür ist eine seitenbündige Startadresse erforderlich und die Länge wird auf die nächste Seitengrenze aufgerundet. Wenn Sie einen Span innerhalb einer freigegebenen Arena freigeben, in der benachbarte Byte noch Live-Objekte enthalten, kann das Aufrunden des Bereichs nach außen dazu führen, dass Live-Zuweisungen, die sich die Grenzseiten teilen, auf null gesetzt werden.
Fragen Sie die Laufzeitseitengröße immer mit sysconf(_SC_PAGESIZE) ab, um sowohl Geräte mit 4‑KB- als auch mit 16‑KB-Seiten zu unterstützen. Runden Sie die interne Startadresse auf die nächste Seitengrenze und die interne Endadresse auf die vorherige Seitengrenze ab. Weitere Informationen zu seitenunabhängigen Allocators finden Sie unter Benutzerdefinierte Speicher-Allocators optimieren.
#include <sys/mman.h>
#include <unistd.h>
#include <cstdint>
// Reclaims physical pages strictly inside [region_start, region_start + length)
// without disturbing live objects on partially occupied boundary pages.
void ReleaseUnusedArenaSpan(void* region_start, size_t length) {
const uintptr_t page_size = static_cast<uintptr_t>(sysconf(_SC_PAGESIZE));
const uintptr_t page_mask = ~(page_size - 1);
const uintptr_t raw_start = reinterpret_cast<uintptr_t>(region_start);
const uintptr_t raw_end = raw_start + length;
// Round start UP and end DOWN so only fully unused pages are discarded.
const uintptr_t aligned_start = (raw_start + page_size - 1) & page_mask;
const uintptr_t aligned_end = raw_end & page_mask;
if (aligned_end > aligned_start) {
madvise(reinterpret_cast<void*>(aligned_start),
aligned_end - aligned_start,
MADV_DONTNEED);
}
}
Wenn Sie benutzerdefinierte anonyme Zuordnungen mit mmap erstellen, kennzeichnen Sie den VMA mit prctl mithilfe von PR_SET_VMA_ANON_NAME, damit Sie die Residency anhand des Namens in showmap und /proc/<pid>/smaps verfolgen können. dumpsys
meminfo zählt anonyme Regionen mit benutzerdefiniertem Namen unter Unknown.
Einige ältere Kernel behalten einen Zeiger auf den Namen, anstatt ihn zu kopieren. Übergeben Sie daher einen String, der für die Lebensdauer der Zuordnung gültig bleibt, z. B. ein Stringliteral:
#include <sys/mman.h>
#include <sys/prctl.h>
#ifndef PR_SET_VMA
#define PR_SET_VMA 0x53564d41
#define PR_SET_VMA_ANON_NAME 0
#endif
void* ReserveNamedArena(size_t capacity_bytes, const char* arena_name) {
void* ptr = mmap(nullptr, capacity_bytes, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr != MAP_FAILED) {
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME,
reinterpret_cast<unsigned long>(ptr), capacity_bytes,
reinterpret_cast<unsigned long>(arena_name));
}
return ptr;
}
Dateicache-Seiten entfernen
Wenn Ihre App Daten aus einem Dateideskriptor liest, speichert der Kernel die Seiten der Datei im Seitencache, um zukünftige Lesevorgänge zu beschleunigen. Dies beschleunigt zwar wiederholte Zugriffe auf kleine Konfigurationsdateien oder freigegebene Bibliotheken, ist aber beim Streamen großer Asset-Archive, Texturpakete oder Audio- und Video-Chunks, die einmal geparst und an anderer Stelle in decodierter Form gespeichert werden, kontraproduktiv.
Mit dem Systemaufruf posix_fadvise kann Ihre Anwendung den Kernel über das erwartete Dateizugriffsmuster für einen offenen Dateideskriptor informieren:
POSIX_FADV_DONTNEED: Weist den Kernel an, dass in naher Zukunft nicht auf den angegebenen Bytebereich zugegriffen wird. Der Kernel entfernt sofort saubere, nicht zugeordnete im Cache gespeicherte Seiten in diesem seitenorientierten Bereich aus dem Seitencache. Dadurch wird der Zählerfileinmemory.statund diememory.current-Gebühr Ihrer cgroup reduziert.POSIX_FADV_SEQUENTIAL: Verdoppelt das Readahead-Fenster des Kernels für den Dateideskriptor und verbessert den Durchsatz beim Streamen eines großen Archivs von Anfang bis Ende. Kombiniere dies mitPOSIX_FADV_DONTNEEDfür nachfolgende Blöcke, während du sie verarbeitest, damit der Speicherbedarf des Seitencaches auf ein kleines gleitendes Fenster beschränkt bleibt.POSIX_FADV_RANDOM: Deaktiviert das spekulative Kernel-Readahead für den Dateideskriptor. Verwenden Sie diese Option, wenn Sie dünnbesetzte, nicht zusammenhängende Suchvorgänge nach Byte-Offset in einer großen Archiv- oder Datenbankdatei durchführen, damit bei jedem kleinen Lesevorgang keine benachbarten ungenutzten Seiten in den Seitencache gezogen werden. Rufen Sie für speicherzugeordnete Dateien stattdessenmadvisemitMADV_RANDOMfür die Zuordnung auf.
Im folgenden Beispiel wird ein großes Asset-Archiv in Blöcken gestreamt. Dabei wird sequenzielles Vorauslesen im Voraus aktiviert und jeder verarbeitete, seitenorientierte Bereich aus dem Seitencache entfernt:
#include <fcntl.h>
#include <unistd.h>
#include <cstdint>
#include <vector>
void StreamAssetArchive(int fd, size_t total_size) {
const size_t page_size = static_cast<size_t>(sysconf(_SC_PAGESIZE));
constexpr size_t kChunkSize = 4 * 1024 * 1024; // 4 MB streaming window
std::vector<uint8_t> buffer(kChunkSize);
// Hint sequential access so the kernel prefetches upcoming chunks.
posix_fadvise(fd, 0, total_size, POSIX_FADV_SEQUENTIAL);
off_t offset = 0;
while (offset < static_cast<off_t>(total_size)) {
ssize_t bytes_read = pread(fd, buffer.data(), buffer.size(), offset);
if (bytes_read <= 0) break;
// Decode or upload buffer[0 .. bytes_read) into runtime structures here.
// Evict full pages that have been completely consumed.
const size_t aligned_len =
(static_cast<size_t>(bytes_read) / page_size) * page_size;
if (aligned_len > 0) {
posix_fadvise(fd, offset, static_cast<off_t>(aligned_len),
POSIX_FADV_DONTNEED);
}
offset += bytes_read;
}
}
Dateigestützte mmap-Regionen zurückfordern
Wenn Ihre App eine große Asset-Datei mit mmap im Arbeitsspeicher abbildet, anstatt sie mit pread zu lesen, werden durch den Aufruf von posix_fadvise mit POSIX_FADV_DONTNEED allein keine Seiten entfernt, die noch in den Seitentabellen Ihres Prozesses abgebildet sind. So löschen Sie einen verbrauchten Bereich einer schreibgeschützten Dateizuordnung, ohne die Zuordnung des gesamten VMA aufzuheben:
- Rufen Sie
madvise(addr, length, MADV_DONTNEED)für den zugeordneten virtuellen Adressbereich auf, um die Zuordnung dieser Seiten zu den Seitentabellen Ihres Prozesses aufzuheben (RssFile-Drops). - Rufen Sie
posix_fadvise(fd, offset, length, POSIX_FADV_DONTNEED)für den entsprechenden Dateioffset auf, damit der Kernel auch die nicht zugeordneten bereinigten Seiten aus dem Seitencache entfernt (fileinmemory.stat-Drops).
Richtlinien für Seitenempfehlungen einhalten
Wende diese Best Practices an, wenn du madvise und posix_fadvise in Allocators, Asset Loader und Lifecycle Handler einbindest:
- Batchaufrufe in grobe Regionen: Jeder
madvise-Aufruf sperrt die Speicherzuordnung Ihres Prozesses (mmap_lock), durchläuft Seitentabellen und macht die entsprechenden TLB-Einträge (Translation Lookaside Buffer) auf anderen CPU-Kernen ungültig (mit Broadcast-TLB-Wartungsanweisungen auf Arm und Interprozessor-Interrupts auf x86). Rufen Siemadvisenicht bei jeder kleinen Freigabe auf. Fassen Sie unbelegte Spans zusammen und informieren Sie den Kernel in Blöcken von 64 KB bis 1 MB oder größer, oder bereinigen Sie unbelegte Spans während Frame-Grenzen und Hintergrundübergängen (onStopundTRIM_MEMORY_UI_HIDDEN). - Leere Kapazität in Hash-Tabellen und Pools zurückfordern: Wenn eine große Hash-Tabelle, ein Objektpool oder ein Staging-Zwischenspeicher nach einem Szenenübergang oder einer Datensynchronisierung geleert wird, verkleinern Sie entweder die zugrunde liegende Zuweisung oder geben Sie
MADV_DONTNEEDfür das geleerte zugrunde liegende Array aus. Wenn Sie eine 32-MB-Tabelle mit offener Adressierung mit Tombstones beibehalten, werden alle 32 MB physischen Seiten Ihrem Prozess in Rechnung gestellt. - Nicht gespeicherte Dateidaten vor dem Entfernen leeren:
POSIX_FADV_DONTNEEDentfernt nur bereinigte Seiten aus dem Seitencache. Wenn Ihre App eine große temporäre Datei oder Cache-Datei schreibt, die nicht sofort wieder gelesen wird, rufen Siefdatasyncvorposix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)auf, damit die geänderten Seiten in den Speicher geschrieben und dann aus dem RAM gelöscht werden. - Häufig genutzten freigegebenen Code oder Assets nicht entfernen: Vermeide es,
POSIX_FADV_DONTNEEDfür DEX-, ODEX-, VDEX- oder gemeinsam genutzte Bibliotheken (.so) oder UI-Assets aufzurufen, auf die wiederholt zugegriffen wird. Das Entfernen aktiver Code- oder Asset-Seiten erzwingt synchrone Speicherfehler, die Threads blockieren und den E/A-Druck erhöhen, wie in den Pressure Stall Information (PSI) angegeben.
Seitenrückgewinnung in der Shell prüfen
Prüfen Sie, ob madvise und posix_fadvise physischen RAM mithilfe von showmap und den cgroup v2-Ressourcenerfassungsdateien Ihrer Anwendung zurückfordern.
Virtuelle und physische Größe mit „showmap“ prüfen
Wenn Sie eine anonyme Arena mit PR_SET_VMA_ANON_NAME benennen, zeigt showmap sowohl die virtuelle Reservierung (virtual size) als auch die residenten physischen Seiten (RSS und PSS) an. Die folgende Beispielausgabe stammt aus der MemoryLab-Beispiel-App im AOSP, die ihre Arena memorylab-arena nennt. Ersetzen Sie com.example.app und memorylab-arena durch den Paketnamen und den Arena-Namen. Führen Sie vor dem Aufruf von madvise mit MADV_DONTNEED für eine berührte 64-MB-Arena Folgendes aus:
adb shell "showmap \$(pidof com.example.app) | grep -E '^ +virtual|^ +size|memorylab-arena' | head -3"
virtual shared shared private private Anon Shmem File Shared Private
size RSS PSS clean dirty clean dirty swap swapPSS HugePages PmdMapped PmdMapped Hugetlb Hugetlb Locked # object
65536 65536 65536 0 0 0 65536 0 0 0 0 0 0 0 0 1 [anon:memorylab-arena]
Führen Sie nach dem Aufruf von madvise(arena_ptr, 64 * 1024 * 1024, MADV_DONTNEED) den Befehl showmap noch einmal aus:
virtual shared shared private private Anon Shmem File Shared Private
size RSS PSS clean dirty clean dirty swap swapPSS HugePages PmdMapped PmdMapped Hugetlb Hugetlb Locked # object
65536 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 [anon:memorylab-arena]
virtual size bleibt bei 65.536 KB (64 MB), was bestätigt, dass der virtuelle Adressbereich weiterhin reserviert ist, während RSS, PSS und private dirty auf 0 KB fallen.
cgroup-Werte „memory.current“ und „memory.stat“ prüfen
Wenn Sie sowohl die Freigabe anonymer Seiten als auch die Entfernung nicht zugeordneter Seiten aus dem Cache überprüfen möchten (was nicht in den Seitentabellen des Prozesses angezeigt wird, wenn sie mit pread gelesen werden), prüfen Sie das cgroup v2-Verzeichnis des Prozesses. Unter Android hat jeder App-Prozess eine eigene cgroup (z. B. /sys/fs/cgroup/apps/uid_<UID>/pid_<PID>/, je nach Gerät).
Mit dem folgenden Befehl wird das Verzeichnis aus der Zeile 0:: von /proc/<PID>/cgroup gesucht und die Zähler werden ausgegeben:
adb shell '
PID=$(pidof com.example.app)
CG=/sys/fs/cgroup$(sed -n "s/^0:://p" /proc/$PID/cgroup)
echo "memory.current: $(cat $CG/memory.current)"
echo "memory.swap.current: $(cat $CG/memory.swap.current)"
grep -E "^(anon|file) " $CG/memory.stat
'
Vor der Freigabe einer anonymen Arena mit 64 MB und dem Entfernen einer speicherzugeordneten Asset-Datei mit 128 MB aus dem Seitencache:
memory.current: 242962432
memory.swap.current: 16633856
anon 100671488
file 140009472
Unmittelbar nach dem Aufruf von madvise mit MADV_DONTNEED für die Arena und die Asset-Dateizuordnung und dann posix_fadvise mit POSIX_FADV_DONTNEED für den Asset-Dateideskriptor:
memory.current: 41766912
memory.swap.current: 16502784
anon 33722368
file 5914624
anon sinkt um 64 MB und file um 128 MB, wodurch memory.current um etwa 192 MB reduziert wird, ohne den Dateideskriptor zu schließen oder die Zuordnung des virtuellen Adressraums der Arena aufzuheben. memory.swap.current ändert sich kaum, da fast keine der Arena-Seiten in ZRAM ausgelagert wurde. Die Werte hängen vom Gerät und davon ab, wie viel der Datei der Kernel bereits selbst entfernt hat. Weitere Informationen dazu, wie der Kernel Speicher pro cgroup berechnet und freigibt, finden Sie unter Speicherfreigabe: Entfernung und Auslagerung.
Seitenrückgewinnung in Perfetto analysieren
Mit Perfetto können Sie madvise- und posix_fadvise-Aufrufe mit Kernel-Seitentabellen- und Seiten-Cache-Zählern auf einer einheitlichen Zeitachse korrelieren.
Trace aufzeichnen
Erfassen Sie einen Trace mit linux.process_stats für regelmäßige Prozesszähler und
linux.ftrace für Echtzeit-RSS- und Seiten-Cache-Ereignisse und übertragen Sie ihn dann auf
Ihren Computer:
adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/page_advice.perfetto-trace <<EOF
buffers: {
size_kb: 65536
fill_policy: RING_BUFFER
}
data_sources: {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
proc_stats_poll_ms: 100
}
}
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "kmem/rss_stat"
ftrace_events: "filemap/mm_filemap_add_to_page_cache"
ftrace_events: "filemap/mm_filemap_delete_from_page_cache"
}
}
}
duration_ms: 20000
EOF
adb pull /data/misc/perfetto-traces/page_advice.perfetto-trace
Tracks in der Perfetto-Benutzeroberfläche prüfen
Öffnen Sie den Trace in der Perfetto-Benutzeroberfläche, maximieren Sie die Prozessgruppe Ihrer App und pinnen Sie die folgenden Zähler-Tracks an, damit sie oben in der Zeitachse bleiben:
mem.virt: Bleibt beiMADV_DONTNEED-Aufrufen unverändert, was bestätigt, dass die Reservierung der virtuellen Adresse (mmap) unverändert ist.mem.rss.anon: Sinkt genau zu dem Zeitstempel stark ab, an dem Ihre AppmadvisemitMADV_DONTNEEDfür Dirty-Anonymous-Arenas oder Heap-Regionen aufruft.mem.rss.file: Wird herabgestuft, wenn Ihre AppmadvisemitMADV_DONTNEEDfür einen dateigestütztenmmap-Bereich aufruft, begleitet von einer Reihe vonmm_filemap_delete_from_page_cache-ftrace-Ereignissen, wennposix_fadvisemitPOSIX_FADV_DONTNEEDdie bereinigten Seiten aus dem Seitencache entfernt.mem.swap: Wird gelöscht, wennMADV_DONTNEEDanonyme Seiten verwirft, die zuvor in ZRAM komprimiert wurden.
Abbildung 2 zeigt diese Tracks für die MemoryLab-Beispiel-App. Wenn Sie dieselbe Ansicht aufrufen möchten, öffnen Sie den Beispiel-Trace in der Perfetto-Benutzeroberfläche.
Spitzen- und endgültigen Arbeitsspeicher mit PerfettoSQL abfragen
Führen Sie die folgende PerfettoSQL-Abfrage in der Perfetto-Benutzeroberfläche (Tab Query (SQL)) aus, um die Spitzen- und Endwerte von mem.virt, mem.rss.anon, mem.rss.file und mem.swap für Ihren Zielprozess zu vergleichen:
WITH counters AS (
SELECT t.name AS counter_name, c.ts, c.value
FROM counter c
JOIN process_counter_track t ON c.track_id = t.id
JOIN process p USING (upid)
WHERE p.name = 'com.example.app'
AND t.name IN ('mem.virt', 'mem.rss.anon', 'mem.rss.file', 'mem.swap')
)
SELECT
counter_name,
CAST(MAX(value) / 1048576 AS INT) AS peak_mb,
CAST((
SELECT value FROM counters AS last
WHERE last.counter_name = counters.counter_name
ORDER BY ts DESC LIMIT 1
) / 1048576 AS INT) AS final_mb
FROM counters
GROUP BY counter_name
ORDER BY counter_name;
Beispielausgabe für den Trace in Abbildung 2:
counter_name peak_mb final_mb
mem.rss.anon 111 48
mem.rss.file 212 84
mem.swap 36 36
mem.virt 13952 13952
mem.rss.anon sinkt um 63 MB und mem.rss.file um 128 MB von ihren Spitzenwerten, während mem.virt am Spitzenwert endet, da die virtuellen Reservierungen unverändert sind.
Entfernte Seiten in Dateien auflösen
In den Ereignissen mm_filemap_add_to_page_cache und mm_filemap_delete_from_page_cache werden Dateien anhand der Inode-Nummer (i_ino) und nicht anhand des Pfads identifiziert. Um zu bestätigen, dass die entfernten Seiten zu der von Ihnen angegebenen Datei gehören, zählen Sie die Entfernungen pro Inode:
SELECT
EXTRACT_ARG(arg_set_id, 'i_ino') AS inode,
COUNT(*) AS evicted_pages
FROM ftrace_event
WHERE name = 'mm_filemap_delete_from_page_cache'
GROUP BY inode
ORDER BY evicted_pages DESC
LIMIT 5;
Beispielausgabe für den Trace in Abbildung 2:
inode evicted_pages
9761 35781
76792192 767
126 668
0 612
11587712 514
Lösen Sie jeden Inode in einen Pfad auf, während das Gerät, das den Trace aufgezeichnet hat, noch verbunden ist. Inode-Nummern sind nur innerhalb eines Dateisystems eindeutig. Beschränken Sie die Suche daher auf das Dateisystem, das die Daten Ihrer Anwendung enthält:
INODE=<inode from the query>
adb shell "find /data -xdev -inum $INODE 2>/dev/null"
Beispielausgabe für Inode 9761:
/data/user/0/com.android.memorylab/files/asset_pack.bin
/data/data/com.android.memorylab/files/asset_pack.bin
Beide Pfade verweisen auf dieselbe Datei, da /data/user/0 und /data/data zwei Namen für dasselbe App-Datenverzeichnis sind. Die Anzahl der Entfernungen für die Asset-Datei (35.781 Seiten) ist größer als die Datei selbst (32.768 Seiten mit 4 KB), da kswapd bereits einen Teil der Datei aufgrund von Speichermangel entfernt hatte, bevor die App sie wieder gelesen und posix_fadvise aufgerufen hat.
Ein Script, das viele Inodes gleichzeitig auflöst, finden Sie unter Fehler in Inodes und dann in Dateien beheben.
← Interaktion zwischen kswapd und lmkd | ↑ Nach oben | Schlanker Hintergrundprozess →