Arbeitsspeicher der App verwalten

Auf dieser Seite wird erläutert, wie Sie die Arbeitsspeichernutzung in Ihrer App proaktiv reduzieren können. Informationen zur Arbeitsspeicherverwaltung im Android-Betriebssystem finden Sie unter Übersicht über die Arbeitsspeicherverwaltung.

Random-Access Memory (RAM) ist eine wertvolle Ressource für jede Softwareentwicklungsumgebung. Für ein mobiles Betriebssystem, bei dem der physische Arbeitsspeicher oft begrenzt ist, ist er noch wertvoller. Sowohl die Android Runtime (ART) als auch die virtuelle Dalvik-Maschine führen eine routinemäßige automatische Speicherbereinigung durch. Das bedeutet jedoch nicht, dass Sie ignorieren können, wann und wo Ihre App Arbeitsspeicher zuweist und freigibt. Sie müssen weiterhin Speicherlecks vermeiden, die in der Regel dadurch verursacht werden, dass Objektverweise in statischen Membervariablen beibehalten werden. Außerdem müssen Sie alle Reference Objekte zum richtigen Zeitpunkt freigeben, wie in den Lifecycle-Callbacks definiert.

Code- und Ressourcenbedarf Ihrer App reduzieren

Einige Ressourcen und Bibliotheken in Ihrem Code können Arbeitsspeicher verbrauchen, ohne dass Sie es bemerken. Die Gesamtgröße Ihrer App, einschließlich Bibliotheken von Drittanbietern oder eingebetteter Ressourcen, kann sich auf die Arbeitsspeichernutzung Ihrer App auswirken. Sie können die Arbeitsspeichernutzung Ihrer App verbessern, indem Sie redundante, unnötige oder aufgeblähte Komponenten, Ressourcen und Bibliotheken aus Ihrem Code entfernen.

Gesamtgröße der App durch Aktivieren von R8 reduzieren

Der kompilierte Anwendungscode ist ein aktiver Teil des Arbeitsspeicherbedarfs zur Laufzeit. Jede Klasse, Methode, Bibliotheksabhängigkeit und jede Stringkonstante muss bei der Ausführung in den Arbeitsspeicher geladen werden. Je größer die kompilierte Codebasis ist, desto mehr physischer Arbeitsspeicher ist für Ihre App erforderlich.

Mit R8 können Sie den Arbeitsspeicherbedarf Ihrer App reduzieren. R8 ist traditionell für die Reduzierung der APK-Größe bekannt, hat aber auch einen direkten positiven Einfluss auf den Arbeitsspeicher zur Laufzeit (RAM). R8 analysiert den Bytecode Ihrer App, um toten Code zu entfernen, redundante Klassen zusammenzuführen, Methoden zu inline und Bezeichner zu minimieren. Wenn weniger kompilierter Bytecode aus dem APK in den Arbeitsspeicher geladen wird, wird der allgemeine Speicherbedarf der App reduziert. Außerdem wird der Arbeitsspeicheraufwand direkt reduziert, indem Klassen-, Methoden- und Feldnamen in kürzere Bezeichner minimiert werden. Optimierungen wie das Zusammenführen von Klassen und das umfangreiche Inlining von Methoden ersetzen auch kostspielige Laufzeit-Lookups und Zuweisungsmuster, was zu einem optimierten Heap- und Stack-Speicher führt.

Keep-Regeln

Keep-Regeln sind Konfigurationsanweisungen, die R8 mitteilen, welche Teile Ihres Codes bei der Optimierung beibehalten werden sollen. So wird verhindert, dass Code entfernt oder minimiert wird, auf den Ihre App angewiesen ist. Weitere Informationen finden Sie unter Übersicht über Keep-Regeln.

Schlecht geschriebene Keep-Regeln verhindern, dass R8 große Teile Ihrer Codebasis optimiert. Vermeiden Sie zu weit gefasste Keep-Regeln und folgen Sie diesen Best Practices:

  • Globale Regeln, die vermieden werden sollten:
    • -dontoptimize: Deaktiviert die Optimierung für die gesamte App vollständig, was zu größeren und langsameren ausführbaren Dateien führt.
    • -dontshrink: Verhindert das Entfernen von nicht verwendetem Code und nicht verwendeten Ressourcen.
    • -dontobfuscate: Verhindert die Minimierung von Namen, wodurch wertvolle Arbeitsspeichereinsparungen verloren gehen (insbesondere bei großen Apps).
  • Platzhalter für das gesamte Paket vermeiden: Weit gefasste Regeln wie -keep class com.example.package.** { *; } zwingen R8, jede Klasse, jedes Feld und jede Methode in diesem Paket beizubehalten. Dadurch wird die Möglichkeit von R8, Code in diesem Paket zu entfernen, zu optimieren oder zu minimieren, vollständig unterbunden.

  • Standardkonfigurationsdatei von R8 verwenden: Verwenden Sie immer proguard-android-optimize.txt.

Weitere Informationen zum Schreiben von Keep-Regeln finden Sie unter der Übersicht über Keep-Regeln. Spezifische Muster, die verwendet und vermieden werden sollten, finden Sie unter Best Practices für Keep-Regeln.

Der R8 Configuration Analyzer bietet Einblicke in Ihre R8-Konfiguration und die Auswirkungen der einzelnen Keep-Regeln auf Ihre App. Weitere Informationen zum Identifizieren von Regeln, die die Optimierung blockieren, finden Sie unter R8 Configuration Analyzer.

Vorsicht bei der Verwendung externer Bibliotheken

Externer Bibliothekscode ist oft nicht für mobile Umgebungen geschrieben und kann für die Arbeit an einem mobilen Client ineffizient sein. Wenn Sie eine externe Bibliothek verwenden, müssen Sie sie möglicherweise für Mobilgeräte optimieren. Planen Sie diese Arbeit im Voraus und analysieren Sie die Bibliothek hinsichtlich Codegröße und Arbeitsspeicherbedarf, bevor Sie sie verwenden.

Auch einige für Mobilgeräte optimierte Bibliotheken können aufgrund unterschiedlicher Implementierungen Probleme verursachen. Beispielsweise kann eine Bibliothek Lite-Protobufs verwenden, während eine andere Micro-Protobufs verwendet, was zu zwei verschiedenen Protobuf-Implementierungen in Ihrer App führt. Dies kann bei verschiedenen Implementierungen von Logging, Analysen, Frameworks zum Laden von Bildern, Caching und vielen anderen Dingen passieren, die Sie nicht erwarten.

Durch die Optimierung Ihrer App mit R8 kann nicht verwendeter Code aus Abhängigkeiten entfernt werden. Die Effektivität ist jedoch oft durch die interne Konfiguration der Bibliothek begrenzt. Beispielsweise können weit gefasste Keep-Regeln oder die Verwendung von Reflection in einer Bibliothek verhindern, dass R8 den Code verkleinert, was zu einem höheren Arbeitsspeicherbedarf führt. Strategien zur Auswahl effizienter Bibliotheken finden Sie unter Bibliotheken mit Bedacht auswählen.

Vermeiden Sie die Verwendung einer gemeinsam genutzten Bibliothek für nur eine oder zwei von Dutzenden von Funktionen. Verwenden Sie nicht eine große Menge an Code und Aufwand, die Sie nicht benötigen. Wenn Sie überlegen, ob Sie eine Bibliothek verwenden möchten, suchen Sie nach einer Implementierung, die Ihren Anforderungen entspricht. Andernfalls können Sie Ihre eigene Implementierung erstellen.

Hilt oder Dagger 2 für die Abhängigkeitsinjektion verwenden

Frameworks für die Abhängigkeitsinjektion können den Code, den Sie schreiben, vereinfachen und eine adaptive Umgebung bieten, die für Tests und andere Konfigurationsänderungen nützlich ist.

Wenn Sie ein Framework für die Abhängigkeitsinjektion in Ihrer App verwenden möchten, sollten Sie Hilt oder Dagger verwenden. Hilt ist eine Bibliothek für die Abhängigkeitsinjektion für Android, die auf Dagger basiert. Dagger verwendet keine Reflection, um den Code Ihrer App zu scannen. Sie können die statische Kompilierzeitimplementierung von Dagger in Android-Apps verwenden, ohne unnötige Laufzeitkosten oder Arbeitsspeichernutzung.

Andere Frameworks für die Abhängigkeitsinjektion, die Reflection verwenden, initialisieren Prozesse, indem sie Ihren Code nach Anmerkungen durchsuchen. Dieser Prozess kann deutlich mehr CPU-Zyklen und Arbeitsspeicher erfordern und beim Starten der App zu einer spürbaren Verzögerung führen.

Achten Sie bei der Verwendung der Abhängigkeitsinjektion darauf, Speicherlecks zu vermeiden, indem Sie sicherstellen, dass Objekte entsprechend ihrem Gültigkeitsbereich definiert sind. Wenn Objekte länger als nötig beibehalten werden, indem sie an den falschen Lebenszyklus gebunden werden, kann dies zu Speicherlecks führen.

Bildladeverhalten bewusst steuern

Grafische Bitmaps sind in der Regel die größten gängigen Objekte im Arbeitsspeicher Ihrer App. Auch wenn Sie mit komprimierten Dateien wie JPEGs arbeiten, muss die Datei in eine unkomprimierte Bitmap umgewandelt werden, um auf dem Bildschirm angezeigt zu werden. Eine kleine komprimierte Bilddatei kann zu einer sehr großen Bitmap werden.

Die meisten Bitmaps verwenden beispielsweise die Konfiguration ARGB_8888, was bedeutet, dass jedes Pixel 4 Byte Arbeitsspeicher benötigt – jeweils ein Byte für Rot, Grün, Blau und Alpha (Transparenz). Wenn Sie ein 100 KB großes JPEG in einer Ansicht mit 1000 × 1000 Pixeln anzeigen, benötigt die Bitmap 4 Byte für jedes dieser 1.000.000 Pixel, was insgesamt 4 MB Arbeitsspeicher ergibt.

Es gibt verschiedene Möglichkeiten, die Verwendung von Bildern zu optimieren. Mit Bibliotheken zum Laden von Bildern können Sie beispielsweise Arbeitsspeicher freigeben, wenn er nicht benötigt wird. Informationen zum effizienten Umgang mit Bildern finden Sie unter Bitmap-Bilder optimieren.

Verfügbaren Arbeitsspeicher und Arbeitsspeichernutzung überwachen

Sie müssen die Probleme mit der Arbeitsspeichernutzung Ihrer App finden, bevor Sie sie beheben können. Mit dem Memory Profiler von Android Studio können Sie Arbeitsspeicherprobleme auf folgende Weise finden und diagnostizieren:

Der Memory Profiler ist auch in die LeakCanary Bibliothek zur Speicherleckerkennung integriert. Mit LeakCanary können Sie die Analyse von Speicherlecks vom Testgerät auf Ihren Entwicklungscomputer verlagern, was Ihren Workflow erheblich beschleunigen kann. Weitere Informationen finden Sie in den Versionshinweisen zu Android Studio.

Es gibt weitere Tools, mit denen Sie Arbeitsspeicherprobleme anhand von Daten von Nutzern diagnostizieren können, die Ihre Produktions-App ausführen:

Arbeitsspeicher als Reaktion auf Ereignisse freigeben

Android kann Arbeitsspeicher von Ihrer App zurückfordern oder Ihre App bei Bedarf vollständig beenden um Arbeitsspeicher für kritische Aufgaben freizugeben. Weitere Informationen finden Sie unter Übersicht über die Arbeitsspeicher verwaltung. Um den Systemspeicher weiter auszugleichen und zu vermeiden, dass das System Ihren App-Prozess beenden muss, können Sie die ComponentCallbacks2 Schnittstelle in Ihren Activity-Klassen implementieren. Die bereitgestellte onTrimMemory() Callback-Methode benachrichtigt Ihre App über Lifecycle- oder arbeitsspeicherbezogene Ereignisse, die eine gute Gelegenheit für Ihre App darstellen, die Arbeitsspeichernutzung freiwillig zu reduzieren. Durch das Freigeben von Arbeitsspeicher kann die Häufigkeit reduziert werden, mit der Ihre App vom Low-Memory-Killer beendet wird.

Ihre Implementierung von onTrimMemory() sollte sich ausschließlich auf die Ereignisse TRIM_MEMORY_UI_HIDDEN und TRIM_MEMORY_BACKGROUND konzentrieren. Ab Android 14 liefert das System keine Benachrichtigungen mehr für die anderen Legacy-Konstanten. Diese Konstanten wurden in Android 15 offiziell als veraltet eingestuft.

  • TRIM_MEMORY_UI_HIDDEN: Dieses Signal gibt an, dass die Benutzeroberfläche Ihrer App nicht mehr für den Nutzer sichtbar ist. Diese Änderung bietet die Möglichkeit, erhebliche Arbeitsspeicherzuweisungen freizugeben, die ausschließlich an die Benutzeroberfläche gebunden sind, z. B. Bitmaps, Videowiedergabepuffer oder komplexe Animationsressourcen.

  • TRIM_MEMORY_BACKGROUND: Dieses Signal gibt an, dass sich Ihr Prozess im Hintergrund befindet und jetzt ein Kandidat für die Beendigung ist, um die globalen Arbeitsspeicheranforderungen des Systems zu erfüllen. Um die Dauer zu verlängern, in der sich Ihr Prozess im Cache befindet, und die Anzahl der Kaltstarts der App zu reduzieren, sollten Sie alle Ressourcen freigeben, die leicht wiederhergestellt werden können, sobald der Nutzer die Sitzung fortsetzt.

Dieses Codebeispiel zeigt, wie Sie den onTrimMemory()-Callback implementieren, um auf verschiedene arbeitsspeicherbezogene Ereignisse zu reagieren:

Kotlin

import android.content.ComponentCallbacks2
// Other import statements.

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    override fun onTrimMemory(level: Int) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

Java

import android.content.ComponentCallbacks2;
// Other import statements.

public class MainActivity extends AppCompatActivity
    implements ComponentCallbacks2 {

    // Other activity code.

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    public void onTrimMemory(int level) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

Arbeitsspeicherbedarf prüfen

Um mehrere laufende Prozesse zu ermöglichen, legt Android ein hartes Limit für die Heap-Größe fest, die für jede App zugewiesen wird. Das genaue Limit für die Heap-Größe variiert je nach Gerät und hängt davon ab, wie viel Arbeitsspeicher insgesamt verfügbar ist. Wenn Ihre App die Heap Kapazität erreicht und versucht, mehr Arbeitsspeicher zuzuweisen, löst das System einen OutOfMemoryError aus.

Um Arbeitsspeichermangel zu vermeiden, können Sie das System abfragen, wie viel Heap-Speicher auf dem aktuellen Gerät verfügbar ist. Sie können das System mit getMemoryInfo() abfragen. Dadurch wird ein ActivityManager.MemoryInfo-Objekt zurückgegeben, das Informationen zum aktuellen Arbeitsspeicherstatus des Geräts enthält, einschließlich des verfügbaren Arbeitsspeichers, des Gesamtspeichers und des Arbeitsspeichergrenzerts – des Arbeitsspeicherniveaus, bei dem das System beginnt, Prozesse zu beenden. Das ActivityManager.MemoryInfo Objekt macht auch lowMemory verfügbar, einen einfachen booleschen Wert, der angibt, ob auf dem Gerät wenig Arbeitsspeicher verfügbar ist.

Das folgende Codebeispiel zeigt, wie Sie die Methode getMemoryInfo() in Ihrer App verwenden.

Kotlin

fun doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    if (!getAvailableMemory().lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
    val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    return ActivityManager.MemoryInfo().also { memoryInfo ->
        activityManager.getMemoryInfo(memoryInfo)
    }
}

Java

public void doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check whether the device is in a low memory state.
    ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();

    if (!memoryInfo.lowMemory) {
        // Do memory intensive work.
    }
}

// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
    ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    return memoryInfo;
}

LMKs überwachen

Für Nutzer sichtbare LMKs (Low Memory Kills) treten auf, wenn der Systemspeicher kritisch niedrig wird. Wenn der Arbeitsspeicher knapp ist, beendet der lmkd (Low Memory Killer Daemon) Prozesse basierend auf ihrem oom_adj_score. Apps, die im Cache gespeichert sind oder einen Dienst ohne zugehörige Benutzeroberfläche ausführen (z. B. einen Job), haben die höchsten Werte und werden zuerst beendet. Wenn der Arbeitsspeicher weiterhin kritisch niedrig ist, muss der Daemon Arbeitsspeicher von Prozessen mit einem oom_adj_score von 0 zurückfordern. Da dieser Wert für sichtbare Apps reserviert ist, führt ihre Beendigung zu einem sofortigen, nicht ordnungsgemäßen Prozessende. Für den Endnutzer sieht es so aus, als wäre die App abgestürzt. Oft werden die Standardmechanismen zum Speichern des Lebenszyklusstatus umgangen, was zu einem Verlust des Nutzerfortschritts führt.

Das Beenden von Vordergrundprozessen ist ein Schwerpunkt von Android Vitals, da sie ein zuverlässiger Proxy für eine schlechte Arbeitsspeicherverwaltung sind. Eine LMK-Rate von mehr als 1% deutet auf einen dringenden Bedarf an sofortigen Maßnahmen hin. Eine niedrige Rate ist jedoch nicht unbedingt ein Indikator für einen guten Zustand. Eine niedrige vom Nutzer wahrgenommene LMK-Rate kann bedeuten, dass der LMK-Daemon häufig Prozesse beendet, während sie im Hintergrund ausgeführt werden. Dies beeinträchtigt die Leistung beim „Warmstart“ und die Multitasking-Flüssigkeit. Daher empfehlen wir, unabhängig von Ihrer aktuellen LMK-Rate die Best Practices für die Arbeitsspeicherverwaltung einzuhalten, um langfristige Stabilität und einen guten Gerätezustand zu gewährleisten.

ProfilingManager verwenden, um Arbeitsspeicherprobleme zu verfolgen

Die Android-Plattform bietet ProfilingManager, eine erweiterte API zur Beobachtbarkeit, mit der Sie Nutzerdaten in der Produktion basierend auf von Ihnen festgelegten Triggern erfassen können. So können Sie schwer zu reproduzierende Arbeitsspeicherprobleme identifizieren.

Zwei neue Trigger, die mit Android 17 eingeführt wurden, sind besonders nützlich, um Arbeitsspeicherprobleme zu erkennen:

Weitere Informationen zur Verwendung von ProfilingManager zum programmatischen Registrieren und Abrufen von Triggern finden Sie in der Dokumentation zum triggerbasierten Profiling.

Sie können auch das appgesteuerte Profiling verwenden, um Start- und Endpunkte für das Tracing manuell zu definieren. Wir empfehlen, dies zu tun, um Heap-Dumps oder Heap-Profile manuell in Bereichen zu erfassen, in denen Sie Speicherlecks oder eine übermäßige Arbeitsspeichernutzung vermuten.

Arbeitsspeichereffizientere Codekonstrukte verwenden

Einige Android-Funktionen, Java-Klassen und Codekonstrukte verwenden mehr Arbeitsspeicher als andere. Sie können die Arbeitsspeichernutzung Ihrer App minimieren, indem Sie in Ihrem Code effizientere Alternativen auswählen.

Dienste sparsam verwenden

Wir empfehlen dringend, Dienste nicht unnötig laufen zu lassen. Unnötige Dienste laufen zu lassen, ist einer der schlimmsten Fehler bei der Arbeitsspeicherverwaltung, die eine Android-App machen kann. Wenn Ihre App einen Dienst benötigt, um im Hintergrund zu arbeiten, lassen Sie ihn nur laufen, wenn ein Job ausgeführt werden muss. Beenden Sie den Dienst, wenn er seine Aufgabe abgeschlossen hat. Andernfalls kann es zu einem Speicherleck kommen.

Wenn Sie einen Dienst starten, lässt das System den Prozess für diesen Dienst bevorzugt laufen. Dieses Verhalten macht Dienstprozesse sehr teuer, da der von einem Dienst verwendete Arbeitsspeicher für andere Prozesse nicht verfügbar ist. Dadurch wird die Anzahl der im Cache gespeicherten Prozesse reduziert, die das System im LRU-Cache behalten kann, was den Wechsel zwischen Apps weniger effizient macht. Bei wenig Arbeitsspeicher kann es sogar zu Thrashing im System kommen, wenn das System nicht genügend Prozesse aufrechterhalten kann, um alle derzeit ausgeführten Dienste zu hosten.

Vermeiden Sie im Allgemeinen die Verwendung persistenter Dienste, da sie den verfügbaren Arbeitsspeicher ständig belasten. Wir empfehlen stattdessen eine alternative Implementierung wie WorkManager. Weitere Informationen zur Verwendung von WorkManager zum Planen von Hintergrund prozessen finden Sie unter Persistente Arbeit.

Optimierte Datencontainer verwenden

Einige der von der Programmiersprache bereitgestellten Klassen sind nicht für die Verwendung auf Mobilgeräten optimiert. Beispielsweise kann die generische HashMap Implementierung ineffizient sein, da für jede Zuordnung ein separates Eintragsobjekt erforderlich ist.

Das Android-Framework enthält mehrere optimierte Datencontainer, darunter SparseArray, SparseBooleanArray und LongSparseArray. Die SparseArray-Klassen sind beispielsweise effizienter, da sie die automatische Boxing-Funktion des Systems für den Schlüssel und manchmal auch für den Wert vermeiden, wodurch ein oder zwei weitere Objekte pro Eintrag erstellt werden.

Bei Bedarf können Sie jederzeit zu Roh-Arrays wechseln, um eine schlanke Datenstruktur zu erhalten.

Vorsicht bei Codeabstraktionen

Entwickler verwenden Abstraktionen oft als Best Practice, da sie die Flexibilität und Wartung des Codes verbessern können. Abstraktionen erfordern jedoch in der Regel mehr Code. Wie unter Code- und Ressourcenbedarf Ihrer App reduzieren beschrieben, erhöht eine größere kompilierte Codebasis direkt den physischen Arbeitsspeicher, den Ihre App benötigt. Wenn Ihre Abstraktionen nicht wesentlich von Vorteil sind, vermeiden Sie sie.

Lite-Protobufs für serialisierte Daten verwenden

Protokollpuffer (Protobufs) sind ein sprach- und plattformneutraler, erweiterbarer Mechanismus, der von Google für die Serialisierung strukturierter Daten entwickelt wurde – ähnlich wie XML, aber kleiner, schneller und einfacher. Wenn Sie Protobufs für Ihre Daten verwenden, verwenden Sie immer Lite-Protobufs in Ihrem clientseitigen Code. Reguläre Protobufs generieren sehr ausführlichen Code, was den Codebedarf Ihrer App im Arbeitsspeicher erhöht (siehe Code- und Ressourcenbedarf Ihrer App reduzieren) und zur Erhöhung der APK-Größe beiträgt.

Weitere Informationen finden Sie in der Protobuf Readme-Datei.

Vorsicht bei Speicherlecks

Eine unsachgemäße Referenzverwaltung kann zu Speicherlecks führen, bei denen Objekte ihre Nutzungsdauer überdauern und der Garbage Collector den Arbeitsspeicher des Lecks nicht zurückfordern kann. Um Speicherlecks zu vermeiden, implementieren Sie ein lebenszyklusbewusstes Design.

Speicher-Churn vermeiden

Ereignisse der automatischen Speicherbereinigung wirken sich nicht auf die Leistung Ihrer App aus. Viele automatische Speicherbereinigungsereignisse, die in kurzer Zeit auftreten, können jedoch den Akku schnell entladen und die Zeit für die Einrichtung von Frames aufgrund der erforderlichen Interaktionen zwischen der automatischen Speicherbereinigung und den App-Threads geringfügig verlängern. Je mehr Zeit das System mit der automatischen Speicherbereinigung verbringt, desto schneller wird der Akku entladen.

Oft kann Speicher-Churn eine große Anzahl von automatischen Speicherbereinigungsereignissen verursachen. In der Praxis beschreibt Speicher-Churn die Anzahl der zugewiesenen temporären Objekte, die in einem bestimmten Zeitraum auftreten.

Beispielsweise können Sie mehrere temporäre Objekte in einer for-Schleife zuweisen. Oder Sie können neue Paint oder Bitmap Objekte in der onDraw() Funktion einer Ansicht erstellen. In beiden Fällen erstellt die App schnell eine große Anzahl von Objekten. Diese können schnell den gesamten verfügbaren Arbeitsspeicher in der jungen Generation verbrauchen, was ein automatische Speicherbereinigung-Ereignis erzwingt.

Verwenden Sie den Speicher-Profiler, um die Stellen in Ihrem Code zu finden, an denen der Memory Churn hoch ist, bevor Sie sie beheben können.

Nachdem Sie die Problembereiche in Ihrem Code identifiziert haben, versuchen Sie, die Anzahl der Zuweisungen in leistungskritischen Bereichen zu reduzieren. Sie können Elemente aus inneren Schleifen entfernen oder sie in eine fabrikbasierte Zuweisungs struktur verschieben.

Sie können auch prüfen, ob Objektpools für den Anwendungsfall von Vorteil sind. Bei einem Objektpool geben Sie eine Objektinstanz nach dem Gebrauch in einen Pool frei, anstatt sie zu löschen. Wenn das nächste Mal eine Objektinstanz dieses Typs benötigt wird, können Sie sie aus dem Pool abrufen, anstatt sie zuzuweisen.

Bewerten Sie die Leistung gründlich, um festzustellen, ob ein Objektpool in einer bestimmten Situation geeignet ist. Es gibt Fälle, in denen Objektpools die Leistung verschlechtern können. Obwohl Pools Zuweisungen vermeiden, führen sie zu anderen Aufwendungen. Die Wartung des Pools umfasst beispielsweise in der Regel die Synchronisierung, die einen nicht unerheblichen Aufwand verursacht. Auch das Löschen der gepoolten Objektinstanz, um Speicherlecks bei der Freigabe zu vermeiden, und die Initialisierung bei der Erfassung können einen nicht unerheblichen Aufwand verursachen.

Wenn mehr Objektinstanzen als nötig im Pool zurückgehalten werden, wird auch die automatische Speicherbereinigung belastet. Objektpools reduzieren zwar die Anzahl der Garbage Collection-Aufrufe, erhöhen aber den Aufwand für jeden Aufruf, da er proportional zur Anzahl der aktiven (erreichbaren) Byte ist.