Zusammenfassung des Android -Speicherlecks
Der Zweck der Speicherverwaltung besteht darin, uns dabei zu helfen, Speicherlecks in unseren Anwendungen während der Entwicklung effektiv zu vermeiden. Jeder ist mit Speicherlecks vertraut. Einfach ausgedrückt bedeutet dies, dass das von einem bestimmten Fall veröffentlichte Objekt, das freigegeben werden sollte, nicht veröffentlicht wurde, aber nicht mehr verwendet wird, damit der GC nicht recycelt werden kann. Vor kurzem habe ich viele relevante Dokumente und Materialien gelesen. Ich habe vor, sie zusammenzufassen und zu regieren und mit Ihnen zu teilen und zu lernen, und ich werde mir auch eine Warnung darüber geben, wie diese Situationen während der Codierung in Zukunft vermieden und die Erfahrung und Qualität der Anwendung verbessert werden können.
Ich werde mit den Grundlagen von Java -Speicherlecks beginnen und spezifische Beispiele verwenden, um die verschiedenen Ursachen für Android -Speicherlecks sowie die Verwendung von Tools zur Analyse von Anwendungsspeicherlecks zu veranschaulichen und schließlich zusammenzufassen.
Java -Speicherzuweisungsstrategie
Es gibt drei Arten von Speicherzuweisungsstrategien, wenn Java -Programme ausgeführt werden, nämlich statische Zuordnung, Stapelzuweisung und Haufen zu Allokation. Entsprechend ist der von den drei Speicherstrategien verwendete Speicherplatz hauptsächlich statischer Speicherbereich (auch als Methodenbereich bezeichnet), Stapelbereich und Haufenbereich.
Statischer Speicherbereich (Methodenbereich): Speichert hauptsächlich statische Daten, globale statische Daten und Konstanten. Diese Erinnerung wird zugewiesen, wenn das Programm kompiliert wird und während des gesamten Programmlaufs existiert.
Stack Area: Wenn eine Methode ausgeführt wird, werden lokale Variablen in der Method -Körperschaft (einschließlich der grundlegenden Datentyp und der Objektreferenz) auf dem Stapel erstellt, und der von diesen lokale Variablen gehaltene Speicher wird am Ende der Methodeausführung automatisch freigegeben. Da der Operation des Stapelspeicherzuweisung in den Befehlssatz des Prozessors integriert ist, ist er sehr effizient, die zugewiesene Speicherkapazität ist jedoch begrenzt.
Heap -Bereich: Auch als dynamische Speicherzuweisung bezeichnet, bezieht sich normalerweise auf den Speicher, der bei Ausführung des Programms direkt neu ist, dh eine Instanz des Objekts. Wenn dieser Teil des Speichers nicht verwendet wird, ist der Java -Müllsammler für das Recycling verantwortlich.
Der Unterschied zwischen Stack und Haufen:
Einige grundlegende Arten von Variablen, die im Methode Körper- und Referenzvariablen von Objekten definiert sind, werden im Stapelspeicher der Methode zugewiesen. Wenn eine Variable in einem Methodenblock definiert wird, weist Java Speicherplatz für die Variable auf dem Stapel zu. Wenn der Umfang der Variablen überschritten wird, ist die Variable ungültig und der dem ihm zugewiesene Speicherplatz wird freigelassen und der Speicherplatz kann wiederverwendet werden.
Der Heap -Speicher wird verwendet, um alle von neuen (einschließlich alle Mitgliedsvariablen im Objekt) und Arrays erstellten Objekte zu speichern. Der im Haufen zugewiesene Speicher wird vom Java -Müllsammler automatisch verwaltet. Nachdem ein Array oder Objekt im Haufen generiert wurde, kann eine spezielle Variable im Stapel definiert werden. Der Wert dieser Variablen entspricht der ersten Adresse des Arrays oder Objekts im Heap -Speicher. Diese spezielle Variable ist die oben erwähnte Referenzvariable. Wir können über diese Referenzvariable auf Objekte oder Arrays im Haufen zugreifen.
Zum Beispiel:
public class sample {int s1 = 0; sample msample1 = new sample (); public void methode () {int s2 = 1; probs msample2 = new sample ();}} sample msample3 = new sample (); Die lokale Variable S2 der Stichprobenklasse und die Referenzvariable msample2 existieren beide auf dem Stapel, aber das durch MSAble2 angezeigte Objekt existiert auf dem Haufen.
Die von MSAbample3 hingewiesene Objektentität wird auf dem Haufen gespeichert, einschließlich aller Mitgliedsvariablen S1 und MSample1 dieses Objekts und existiert sich im Stapel.
abschließend:
Die grundlegenden Datentypen und Referenzen lokaler Variablen werden im Stapel gespeichert und die referenzierten Objektentitäten werden im Haufen gespeichert. - Da sie zu Variablen in Methoden gehören, endet der Lebenszyklus mit Methoden.
Mitgliedervariablen werden alle gespeichert und im Heap (einschließlich grundlegender Datentypen, Bezug genommen und referenziert) - da sie zu Klassen gehören, werden schließlich Klassenobjekte für die neue Verwendung verwendet.
Nachdem wir Javas Speicherzuweisung verstanden haben, schauen wir uns an, wie Java das Gedächtnis verwaltet.
Wie Java den Speicher verwaltet
Die Speicherverwaltung von Java ist das Problem der Objektzuweisung und der Veröffentlichung. In Java müssen Programmierer über das Keyword neu (mit Ausnahme von Grundtypen) für jedes Objekt Speicherplatz anwenden, und alle Objekte vergeben Platz im Heap (Heap). Zusätzlich wird die Freisetzung von Objekten vom GC bestimmt und ausgeführt. In Java erfolgt die Speicherzuweisung durch Programme, während die Speicherfreigabe von GC durchgeführt wird. Diese Umsatz- und Ausgabenmethode für zwei Zeilen vereinfacht die Arbeit von Programmierern. Gleichzeitig trägt es auch zur Arbeit des JVM bei. Dies ist auch einer der Gründe, warum Java -Programme langsamer laufen. Denn um Objekte korrekt freizugeben, muss GC den laufenden Status jedes Objekts, einschließlich der Anwendung, des Zitats, des Zitats, der Zuordnung usw. des Objekts, überwachen, und GC muss es überwachen.
Die Überwachung des Zustands eines Objekts besteht darin, das Objekt genauer und rechtzeitig freizusetzen, und das Grundprinzip der Freisetzung des Objekts ist, dass das Objekt nicht mehr verwiesen wird.
Um besser zu verstehen, wie GC funktioniert, können wir das Objekt als einen Scheitelpunkt eines gerichteten Diagramms und die Referenzbeziehung als gerichtete Kanten des Graphen betrachten, die vom Referencer auf das referenzierte Objekt hinweisen. Zusätzlich kann jedes Thread -Objekt als Startscheitelpunkt eines Diagramms verwendet werden. Beispielsweise beginnen die meisten Programme vom Hauptprozess, sodass der Diagramm ein Wurzelbaum ist, der mit dem Hauptprozessscheitelpunkt beginnt. In diesem gerichteten Diagramm sind Objekte, die vom Stammscheitelpunkt erreichbar sind, gültige Objekte, und GC recycelt diese Objekte nicht. Wenn ein Objekt (angeschlossener Untergraph) von diesem Root -Scheitelpunkt nicht erreichbar ist (beachten Sie, dass der Diagramm ein gerichtetes Diagramm ist), dann glauben wir, dass dieses (diese) Objekt nicht mehr verwiesen wird und von GC recycelt werden kann.
Im Folgenden geben wir ein Beispiel dafür, wie man gerichtete Diagramme verwendet, um die Speicherverwaltung darzustellen. Für jeden Moment des Programms haben wir ein angegebenes Diagramm, das die Speicherzuweisung des JVM darstellt. Das Bild unten ist ein Diagramm des Programms links, der zu Zeile 6 läuft.
Java verwendet gerichtete Graphen für die Speicherverwaltung, die das Problem von Referenzschleifen beseitigen kann. Zum Beispiel gibt es drei Objekte, die sich aufeinander beziehen. Solange sie und der Wurzelprozess nicht erreichbar sind, kann GC sie auch recyceln. Der Vorteil dieser Methode besteht darin, dass sie eine hohe Präzision beim Verwalten von Speicher aufweist, jedoch eine geringe Effizienz ist. Eine weitere häufig verwendete Speicherverwaltungstechnologie besteht darin, Zähler zu verwenden. Zum Beispiel verwendet das COM -Modell die Zählermethode, um Komponenten zu verwalten. Im Vergleich zu gerichteten Graphen weist es niedrige Präzisionslinien auf (es ist schwierig, mit kreisförmigen Referenzproblemen umzugehen), hat jedoch eine hohe Ausführungseffizienz.
Was ist ein Speicherleck in Java
In Java sind Speicherlecks die Existenz einiger zugewiesener Objekte, die die folgenden zwei Merkmale aufweisen. Erstens sind diese Objekte erreichbar, dh im gerichteten Diagramm gibt es Pfade, die mit ihnen verbunden werden können. Zweitens sind diese Objekte nutzlos, dh das Programm wird diese Objekte in Zukunft nicht wieder verwenden. Wenn das Objekt diese beiden Bedingungen erfüllt, können diese Objekte als Speicherleck in Java bestimmt werden, und diese Objekte werden nicht von GC recycelt, sondern nimmt den Speicher ein.
In C ++ haben Speicherlecks einen größeren Bereich. Einige Objekte haben Speicherplatz zugewiesen, aber dann nicht erreichbar. Da es in C ++ keinen GC gibt, werden diese Speicher niemals gesammelt. In Java werden diese unerreichbaren Objekte von GC recycelt, sodass Programmierer diesen Teil des Speicherlecks nicht berücksichtigen müssen.
Durch die Analyse wissen wir, dass Programmierer für C ++ Kanten und Scheitelpunkte selbst verwalten müssen, während sie für Java -Programmierer nur Kanten verwalten müssen (nicht erforderlich, um die Freigabe von Scheitelpunkten zu verwalten). Auf diese Weise verbessert Java die Programmierungseffizienz.
Daher wissen wir durch die obige Analyse, dass es auch Speicherlecks in Java gibt, aber der Umfang ist kleiner als der von C ++. Da die Java -Sprache garantiert, dass jedes Objekt erreichbar ist, werden alle nicht erreichbaren Objekte von GC verwaltet.
Für Programmierer ist GC im Grunde transparent und unsichtbar. Obwohl wir nur wenige Funktionen haben, um auf GC zuzugreifen, wie z. B. System.gc (), das GC gemäß der Definition der Java -Sprachspezifikation ausführt, garantiert diese Funktion nicht, dass der Müllsammler des JVM ausgeführt wird. Da verschiedene JVM -Implementierer unterschiedliche Algorithmen verwenden können, um GC zu verwalten. Im Allgemeinen haben GCs Threads eine geringere Priorität. Es gibt viele Strategien für JVM, um GC anzurufen. Einige von ihnen arbeiten erst, wenn die Speicherverwendung ein bestimmtes Niveau erreicht. Einige führen sie regelmäßig aus. Einige führen GC reibungslos aus und einige führen GC in Interrupt -Weise aus. Aber im Allgemeinen müssen wir uns nicht darum kümmern. Sofern in bestimmten Situationen nicht die Ausführung von GC die Leistung der Anwendung beeinflusst. Für webbasierte Echtzeit-Systeme wie Online-Spiele möchten Benutzer beispielsweise nicht, dass GC die Ausführung von Anwendungen plötzlich unterbricht und die Müllsammlung durchführt, dann müssen wir die Parameter von GC so anpassen, dass GC das Speicher auf reibungslose Weise freien Speicher kann, z. Der von Sun bereitgestellte Hotspot JVM unterstützt diese Funktion.
Gibt auch ein typisches Beispiel für Java -Speicherleck.
Vektor v = neuer Vektor (10); für (int i = 1; i <100; i ++) {Objekt o = neues Objekt (); v.Add (o); o = null; }In diesem Beispiel wenden wir uns für den Objektobjektzyklus an und setzen das angelegte Objekt in einen Vektor ein. Wenn wir nur die Referenz selbst freigeben, bezieht sich der Vektor immer noch auf das Objekt, sodass dieses Objekt für GC nicht recycelbar ist. Wenn das Objekt nach dem Hinzufügen des Vektors aus dem Vektor gelöscht werden muss, besteht der einfachste Weg, das Vektorobjekt auf Null zu setzen.
Speicherleck in detaillierten Java
1. Java -Speicherrecyclingmechanismus
Unabhängig von der Speicherzuordnungsmethode einer Sprache müssen die tatsächliche Adresse des zugewiesenen Speichers zurückgegeben, dh einen Zeiger an die erste Adresse des Speicherblocks zurückzugeben. Objekte in Java werden mit neuen oder Reflexionsmethoden erstellt. Die Erstellung dieser Objekte wird im Haufen zugewiesen. Alle Objekte werden von der java virtuellen Maschine über einen Müllsammlungsmechanismus gesammelt. Um Objekte korrekt zu veröffentlichen, überwacht GC den Gesundheitszustand jedes Objekts und überwacht deren Anwendung, Zitat, Zitat, Zuordnung usw. Java wird gerichtete Graphenmethoden verwendet, um den Speicher zu verwalten, um zu überwachen, ob das Objekt in Echtzeit erreicht werden kann. Wenn es nicht erreicht ist, wird es recycelt, was auch das Problem der Referenzschleifen beseitigen kann. In der Java -Sprache gibt es zwei Arten von Speicherraum, die festlegen, ob ein Speicherraum die Müllsammlungskriterien erfüllt: Eine soll dem Objekt, das nicht unten aufgerufen wurde, einen leeren Wert zuzuweisen, und der andere ist, dem Objekt einen neuen Wert zuzuweisen und so den Speicherplatz zu neu zuzuordnen.
2. Ursachen des Java -Speicherlecks
Speicherleck bezieht sich auf das kontinuierliche nutzlose Objekt (das nicht mehr verwendet wird) oder der Speicher von nutzlosen Objekten kann nicht rechtzeitig freigegeben werden, was zu einem Speicherraumverschwendung führt, der als Speicherleck bezeichnet wird. Speicherlecks sind manchmal nicht schwerwiegend und nicht leicht zu erkennen, sodass Entwickler nicht wissen, dass es ein Speicherleck gibt, aber manchmal kann es sehr ernst sein und veranlassen Sie, aus dem Gedächtnis herauszukommen.
Was ist die Grundursache für Java -Speicherleck? Wenn ein Long-Life-Zyklusobjekt einen Verweis auf ein Kurzzeit-Zyklusobjekt enthält, ist es wahrscheinlich, dass ein Speicherleck auftritt. Obwohl ein Kurzzeit-Zyklusobjekt nicht mehr benötigt wird, kann es nicht recycelt werden, da es seine Referenz für einen Langzeitzyklus enthält. Dies ist das Szenario, in dem Speicherlecks in Java auftreten. Es gibt hauptsächlich die folgenden Kategorien:
1. Die statische Sammlungsklasse verursacht Speicherleck:
Die Verwendung von HashMap, Vektor usw. tritt am wahrscheinlichsten in Speicherlecks auf. Der Lebenszyklus dieser statischen Variablen stimmt mit dem der Anwendung überein. Alle Objekte, auf die sie verweisen, können nicht freigegeben werden, da sie auch von Vector usw. verwiesen werden.
Zum Beispiel
Statischer Vektor v = neuer Vektor (10); für (int i = 1; i <100; i ++) {Objekt o = neues Objekt (); v.Add (o); o = null;}In diesem Beispiel wird das Objektobjekt angewendet und das angelegte Objekt in einen Vektor gegeben. Wenn die Referenz selbst nur freigegeben wird (o = null), verweist der Vektor immer noch das Objekt, sodass dieses Objekt für GC nicht recycelbar ist. Wenn das Objekt nach dem Hinzufügen des Vektors aus dem Vektor gelöscht werden muss, besteht der einfachste Weg, das Vektorobjekt auf Null zu setzen.
2. Wenn die Objekteigenschaften in der Sammlung geändert werden, funktioniert die Methode von REME () nicht.
Zum Beispiel:
public static void main (String [] args) {set <Person> set = new Hashset <person> (); Person p1 = new Person ("Tang Monk", "pwd1", 25); Person p2 = neue Person ("Sun Wukong", "pwd2", 26); Person p3 = neue Person ("Zhu -Person (" Zhu (Zhu "(" Zhu (Zhu Bajie "," pwd3 ", 27); set.add (p1); set.add (p2); set.add (p3); System.out.println (" Es gibt insgesamt: "+set.size ()+" Elemente! "); // Ergebnis: Es gibt insgesamt: 3 Elemente! P3.Setage (2); // das Alter von P3 und den HashCode -Wert modifizieren, der den zu diesem Zeitpunkt der P3 -Elementänderung entspricht zu diesem Zeitpunkt entspricht. Remove (p3); // Entfernen Sie es zu diesem Zeitpunkt, wodurch der Speicher -Leckage -Satz (P3) verursacht wird. // es erneut hinzufügen und es wurde erfolgreich system.out.println hinzugefügt ("Es gibt:"+set.size ()+"Elemente!"); // Ergebnis: Es gibt: insgesamt 4 Elemente! für (Person Person: set) {System.out.println (Person);}}3. Hörer
In der Java -Programmierung müssen wir alle mit Zuhörern zu tun haben. Normalerweise werden viele Zuhörer in einer Anwendung verwendet. Wir werden eine Steuermethode wie addxxxxListener () aufrufen, um die Hörer hinzuzufügen. Wenn wir das Objekt jedoch häufig veröffentlichen, müssen wir diese Hörer nicht löschen und so die Wahrscheinlichkeit von Speicherlecks erhöhen.
4. Verschiedene Verbindungen
Beispielsweise werden die Datenbankverbindung (DataSourse.getConnection ()), Netzwerkverbindung (Socket) und IO -Verbindungen von GC nicht automatisch recycelt, es sei denn, sie fordert seine Methode close () zum Schließen seiner Verbindung explizit auf. Das Ergebnis- und Anweisungsobjekte können nicht explizit recycelt werden, aber die Verbindung muss explizit recycelt werden, da die Verbindung zu keinem Zeitpunkt automatisch recycelt werden kann. Sobald die Verbindung recycelt ist, werden die Folge- und Anweisungsobjekte sofort null. Wenn Sie jedoch einen Verbindungspool verwenden, ist die Situation unterschiedlich. Zusätzlich zum expliziten Schließen der Verbindung müssen Sie das Ergebnis von Ergebnisset -Anweisungen explizit schließen (das andere schließt auch, der andere wird ebenfalls geschlossen), andernfalls wird eine große Anzahl von Anweisungsobjekten nicht freigegeben, was Speicherlecks verursacht. In diesem Fall wird die Verbindung normalerweise im Versuch und schließlich freigegeben.
5. Verweise auf interne Klassen und externe Module
Verweise auf interne Klassen sind relativ leicht zu vergessen, und sobald sie nicht veröffentlicht wurden, werden möglicherweise keine Reihe von Nachfolgerklassenobjekten veröffentlicht. Darüber hinaus sollten Programmierer auch auf versehentliche Hinweise auf externe Module vorsichtig sein. Beispielsweise ist Programmierer A für das Modul A verantwortlich und nennt eine Methode des Moduls B wie:
public void registersg (Objekt B);
Diese Art von Anruf erfordert große Sorgfalt. Wenn ein Objekt übergeben wird, ist es sehr wahrscheinlich, dass das Modul B einen Bezug auf das Objekt beibehält. Zu diesem Zeitpunkt müssen Sie darauf achten, ob das Modul B entsprechende Vorgänge zum Entfernen von Referenzen liefert.
6. Singleton -Modus
Eine falsche Verwendung des Singleton -Musters ist ein häufiges Problem, das Speicherlecks verursacht. Singleton -Objekte werden während des gesamten Lebenszyklus des JVM nach der Initialisierung (in Form statischer Variablen) existieren. Wenn das Singleton -Objekt externe Referenzen enthält, wird dieses Objekt nicht normal von der JVM recycelt, was zu Speicherlecks führt. Betrachten Sie das folgende Beispiel:
class A{public A(){B.getInstance().setA(this);}....}//Class B uses the singleton mode class B{private A a;private static B instance=new B();public B(){}public static B getInstance(){return instance;}public void setA(A a){this.a=a;}//getter...}Offensichtlich nimmt B das Singleton -Muster an, das einen Hinweis auf ein Objekt A hat, und das Objekt dieser Klasse A wird nicht recycelt. Stellen Sie sich vor, was passieren würde, wenn A ein komplexeres Objekt- oder Sammelart wäre
Zusammenfassung gemeinsamer Speicherlecks in Android
Sammelklassenleck
Wenn die Sammelklasse nur über eine Methode verfügt, um Elemente hinzuzufügen und keinen entsprechenden Löschmechanismus aufweist, wird der Gedächtnis besetzt. Wenn es sich bei dieser Sammelklasse um eine globale Variable handelt (z. B. statische Eigenschaften in der Klasse, globale Karte usw., dh eine statische Referenz oder einen endgültigen Hinweis auf sie ständig), gibt es keinen entsprechenden Löschmechanismus, der dazu führen kann, Zum Beispiel ist das typische Beispiel oben eine dieser Situationen. Natürlich werden wir definitiv keinen solchen 2B -Code in das Projekt schreiben, aber es ist immer noch einfach, wenn wir nicht vorsichtig sind. Zum Beispiel machen wir alle gerne ein paar Caches durch Hashmap, daher sollten wir in dieser Situation vorsichtiger sein.
Speicherleck durch Singletons verursacht
Da die statische Natur eines Singletons seinen Lebenszyklus so lange macht, wie der Lebenszyklus der Anwendung unangemessen verwendet wird, ist es leicht, Speicherleckage zu verursachen. Zum Beispiel das folgende typische Beispiel,
öffentliche Klasse AppManager {private statische AppManager -Instanz; privater Kontext -Kontext; private AppManager (Kontextkontext) {this.context = context;} public static AppManager getInstance (Kontextkontext) {if (Instance == null) {Instance = New AppManager (Kontext);} return Instance;}}}}}}}Dies ist ein normales Singleton -Muster. Beim Erstellen dieses Singletons, da ein Kontext übergeben werden muss, ist die Länge des Lebenszyklus dieses Kontextes von entscheidender Bedeutung:
1. Wenn der Kontext der Anwendung zu diesem Zeitpunkt übergeben wird, da der Lebenszyklus der Anwendung der Lebenszyklus der gesamten Anwendung ist, wird es kein Problem geben.
2. Wenn der Aktivitätskontext zu diesem Zeitpunkt übergeben wird, wenn die Aktivität, die diesem Kontext entspricht,, da der Verweis auf den Kontext von einem Singleton -Objekt gehalten wird, entspricht sein Lebenszyklus dem gesamten Anwendungslebenszyklus. Wenn die Aktivität verlässt, wird ihr Speicher nicht recycelt, was zu einem Leck führt.
Der richtige Weg sollte in Folgendes geändert werden:
public class AppManager {private static AppManager instance;private Context context;private AppManager(Context context) {this.context = context.getApplicationContext();// context using Application}public static AppManager getInstance(Context context) {if (instance == null) {instance = new AppManager(context);}return instance;}}Oder schreiben Sie auf diese Weise und Sie müssen den Kontext nicht einmal übergeben in:
Fügen Sie Ihrer Anwendung eine statische Methode hinzu. GetContext () gibt den Kontext der Anwendung zurück.
...
context = getApplicationContext (); .../*** Global Context*@Return Return Global Context Object*/public static Context getContext () {return context;} public class AppManager {private static AppManagerinstanz; private context context; private AppManager () {context = my myApplication. {if (instance == null) {instance = new AppManager ();} return Instance;}}Anonyme innere Klassen/nicht statische Innenklassen und asynchrone Themen
Speicherleck, das durch Erstellen statischer Instanzen in nicht statischen internen Klassen verursacht wird
Manchmal können wir häufig mit Aktivitäten beginnen. Um wiederholt die gleichen Datenressourcen zu erstellen, kann diese Art des Schreibens auftreten:
Die Mainaktivität der Öffentlichkeitsklasse erweitert die AppCompataktivität (private statische testResource mresource = null; @OverrideProtected void Oncreate (Bündel SavedInstancestate) {Super.oncreate (SavedInstancestate); TestResource ();} // ...} Klasse TestResource {// ...}}Dies schafft einen Singleton einer nicht statischen inneren Klasse innerhalb der Aktivität, und die Daten des Singletons werden jedes Mal verwendet, wenn die Aktivität begonnen wird. Obwohl die wiederholte Schaffung von Ressourcen vermieden wird, wird dieses Schreiben Speicherlecks verursachen, da die nicht statische innere Klasse standardmäßig auf externe Klassen verweist, und die nicht statische innere Klasse eine statische Instanz erzeugt, und der Lebenszyklus der Instanz ist so lang wie die Anwendung, die die statische Instanz, die sich immer auf die Aktivität verweisen, die sich auf die Aktivität ergeben. Der richtige Weg, dies zu tun, ist:
Stellen Sie die innere Klasse als statische innere Klasse ein oder extrahieren Sie die innere Klasse und verkapulieren sie in einen Singleton. Wenn Sie den Kontext verwenden müssen, befolgen Sie bitte den oben empfohlenen Kontext, um die Anwendung zu verwenden. Natürlich ist der Kontext der Anwendung nicht allmächtig, daher kann er nicht zufällig verwendet werden. An einigen Stellen müssen Sie den Kontext der Aktivität verwenden. Die Anwendungsszenarien des Kontextes von Anwendung, Dienst und Aktivität sind wie folgt:
Wo: NO1 bedeutet, dass Anwendung und Dienst eine Aktivität starten können, aber es muss eine neue Task -Warteschlange erstellt werden. Für den Dialog kann es nur in der Aktivität erstellt werden
Anonyme interne Klasse
Die Androidentwicklung erbt häufig die Implementierung von Aktivität/Fragment/Sicht. Wenn Sie zu diesem Zeitpunkt anonyme Klassen verwenden und von asynchronen Threads gehalten werden, sollten Sie vorsichtig sein. Wenn es keine Maßnahme gibt, wird es definitiv zu Leckagen führen.
Mainaktivität der Öffentlichkeit Klasse erweitert Aktivität {... runnable ref1 = new Myrunable (); Runnable ref2 = new Runnable () {@Overridepublic void run () {}}; ...}Der Unterschied zwischen Ref1 und Ref2 besteht darin, dass Ref2 anonyme innere Klassen verwendet. Werfen wir einen Blick auf den zur Laufzeit verwiesenen Speicher:
Wie Sie sehen können, ist Ref1 nichts Besonderes.
Das Implementierungsobjekt der anonymen Klasse Ref2 gibt jedoch eine zusätzliche Referenz:
Diese Referenz von 0 USD zeigt auf MainActivity. Dies, dh die aktuelle Mainaktivitätsinstanz, wird von Ref2 gehalten. Wenn diese Referenz in einen asynchronen Faden übergeben wird und dieser Thread und dieser Aktivitätslebenszyklus inkonsistent sind, wird das Aktivitätsleck verursacht.
Speicherleck durch Handler verursacht
Das durch die Verwendung von Handler verursachte Speicher -Leck -Problem sollte als am häufigsten angesehen werden. Um ANR zu vermeiden, führen wir keine zeitaufwändigen Vorgänge auf dem Hauptfaden aus und verwenden den Handler, um Netzwerkaufgaben zu erledigen oder einige Anforderungs-Rückrufe und andere APIs zu verkapulieren. Handler ist jedoch nicht allmächtig. Wenn der Code des Handlers standardisiert geschrieben ist, kann er Speicherlecks verursachen. Darüber hinaus wissen wir, dass Handler, Nachrichten und Messagequeue alle miteinander zu tun haben. Für den Fall, dass die vom Handler gesendete Nachricht noch nicht verarbeitet wurde, wird die Nachricht und das Handlerobjekt, das sie gesendet hat, vom Thread Messagequeue gehalten.
Da Handler zu TLS -Variablen (Lokalspeicher) gehört, sind Lebenszyklus und Aktivität inkonsistent. Daher ist diese Implementierungsmethode im Allgemeinen schwierig sicherzustellen, dass sie mit dem Lebenszyklus der Sicht oder Aktivität übereinstimmt. Daher ist es einfach, die richtige Freisetzung zu verursachen.
Zum Beispiel:
öffentliche Klasse Sampleaktivität erweitert die Aktivität {private endgültige Handler mleakyHandler = new Handler () {@Overridepublic void Handlemessage (Message MSG) {// ...}}@überschüssige void void Oncreate (Bündel savedInstancestate). minute.mleakyHandler.postdelayed (new Runnable () {@Overridepublic void run () {/ * ... */}}, 1000 * 60 * 10); // Gehen Sie zur vorherigen Aktivität zurück.finish ();}}Eine Nachrichtenmeldung verzögert die Ausführung von 10 Minuten wird in der Stichprobe deklariert, und MleakyHandler drückt sie in die Message Queue Messagequeue. Wenn die Aktivität durch Finish () fallen gelassen wird, wird die Meldung, die die Ausführung der Aufgabe verzögert, weiterhin im Haupt-Thread existiert, der die Handler-Referenz der Aktivität hält, sodass die Aktivität durch Finish () nicht recycelt wird, was die Speicherleckage verursacht (weil der Handler eine nicht statische Innenklasse ist, die sich auf die externe Klassen befindet, die sich auf die Samplectivität befindet, die sich hier auf Samplectivctivity bezieht).
Fix: Vermeiden Sie es, nicht statische innere Klassen in der Aktivität zu verwenden. Wenn wir beispielsweise den Handler als statisch erklären, hat seine Überlebenszeit nichts mit dem Lebenszyklus der Aktivität zu tun. Gleichzeitig wird die Aktivität durch schwache Hinweise eingeführt, um die direkte Übergabe der Aktivität als Kontext direkt zu verhindern. Siehe den folgenden Code:
Die Probenpleaktivität der öffentlichen Klasse erweitert die Aktivität. {/*** Instanzen statischer innerer Klassen haben keinen impliziten*Verweis auf ihre äußere Klasse.*/Private statische Klasse MyHandler erweitert Handler (private endgültige schwacheReferation <sampleactivity> maktivität; public myHandler (sampleaktivitätsaktivität) {mactivity = newnowrescreference <samplectivity> (Aktivität)@@über. Handlemessage (Message MSG) {Sampleaktivitätsaktivität = maktivität.get (); if (Aktivität! {@Overridepublic void run () {/ * ... */}};@überschreibete void oncreate (Bündel savedInstancestate) {super.oncreate (savedInstancestate); // eine Nachricht posten und ihre Ausführung für 10 Minuten verzögern. Aktivität.finish ();}}Übersicht wird empfohlen, die statische innere Klasse + Wecreference zu verwenden. Achten Sie darauf, vor jedem Gebrauch leer zu sein.
Weepreference wurde früher erwähnt, also werde ich hier kurz über mehrere Referenztypen von Java -Objekten sprechen.
Java hat vier Kategorien von Referenzen: starke Referenz, Weichweite, Schwache und Phatomreferenz.
Bei der Entwicklung von Android -Anwendungen, um den Speicherüberlauf zu verhindern, können bei einigen Objekten, die ein großer Speicher einnehmen und einen langen Deklarationszyklus haben, so weit wie möglich verwendet werden.
Weiche/schwache Referenzen können in Verbindung mit einer Referenzwarteschlange (Referenz) verwendet werden. Wenn das von der sanften Referenz verwiesene Objekt vom Garbage Collector recycelt wird, fügt die virtuelle Java -Maschine die weiche Referenz zur zugehörigen Referenzwarteschlange hinzu. Mit dieser Warteschlange können Sie die recycelte Liste von weichen/schwachen Referenzen kennen und so den Puffer, der fehlgeschlagene weiche/schwache Referenzen gescheitert ist, zu beseitigen.
Angenommen, unsere Anwendung verwendet eine große Anzahl von Standardbildern, wie z. B. den Standard -Avatar, das Standard -Spielsymbol usw., die an vielen Stellen verwendet werden. Wenn Sie das Bild jedes Mal lesen, ist es langsamer, da das Lesen der Datei den Hardwareoperation erfordert, was zu einer geringeren Leistung führt. Deshalb betrachten wir das Bild Cache und lesen es bei Bedarf direkt aus dem Speicher. Da Bilder jedoch viel Speicherplatz in Anspruch nehmen und viele Bilder für viel Speicher benötigen, kann es wahrscheinlicher sind, dass Ausnahmen auftreten. Zu diesem Zeitpunkt können wir in Betracht ziehen, weiche/schwache Referenztechniken zu verwenden, um dieses Problem zu vermeiden. Das Folgende ist der Prototyp des Cache:
Definieren Sie zuerst eine Hashmap und speichern Sie das Soft -Referenzobjekt.
private map <String, SofTreference <Bitmap >> imagebach = new HashMap <String, Softreference <Bitmap >> ();
Definieren wir eine Methode, um die weiche Referenz von Bitmap auf HashMap zu speichern.
Nach Verwendung weicher Referenzen kann der Speicherplatz dieser zwischengespeicherten Bildressourcen vor der Ausnahme von OutofMemory befreit werden, wodurch verhindert wird, dass der Speicher die Obergrenze erreicht und einen Absturz vermeidet.
Wenn Sie nur das Auftreten einer Ausnahme in der Ausmemory vermeiden möchten, können Sie weiche Referenzen verwenden. Wenn Sie sich mehr für die Leistung Ihrer Anwendung interessieren und einige Objekte recyceln möchten, die so bald wie möglich mehr Speicher haben, können Sie schwache Referenzen verwenden.
Zusätzlich können Sie feststellen, ob das Objekt häufig verwendet wird, um zu bestimmen, ob es als weiche Referenz oder eine schwache Referenz ausgewählt wird. Wenn das Objekt häufig verwendet werden kann, versuchen Sie, weiche Referenzen zu verwenden. Wenn das Objekt nicht wahrscheinlicher verwendet wird, kann es mit schwachen Referenzen verwendet werden.
OK, kehren Sie weiter zum Thema zurück. Wie bereits erwähnt, erstellen Sie eine statische Innenklasse und verwenden Sie schwache Verweise auf die vom Handler gehaltenen Objekte, damit die vom Handler gehaltenen Objekte auch während des Recyclings recycelt werden können. Obwohl dies Aktivitätsleckage vermeidet, können in der Meldungswarteschlange des Looper -Threads noch Nachrichten anhängig sein. Daher sollten wir die Nachrichten in der Nachrichtenwarteschlange während der Zerstörung oder des Stopps der Aktivität entfernen.
Die folgenden Methoden können die Nachricht entfernen:
Public Final Leere RemoveCallbacks (Runnable R); öffentliche endgültige Leere RemoveCallbacks (Runnable R, Object Token); öffentliche endgültige Leere removecallbacksandMessages (Objekt -Token); öffentliche endgültige Leere Entfernung (int was); öffentliche endgültige Void -Entfernung (int was, Objektobjekt);
Versuchen Sie, statische Mitgliedsvariablen zu vermeiden
Wenn eine Mitgliedsvariable als statisch erklärt wird, wissen wir alle, dass ihr Lebenszyklus mit dem gesamten App -Prozesslebenszyklus übereinstimmt.
Dies führt zu einer Reihe von Problemen. Wenn Ihr App-Prozess so ausgelegt ist, dass der Speicher ansässig ist, wird dieser Teil des Speichers auch dann nicht veröffentlicht, wenn die App den Hintergrund abschneidet. Gemäß dem aktuellen Speicherverwaltungsmechanismus mobiler Apps werden zuerst Hintergrundprozesse, die eine große Menge an Speicher ausmachen, recycelt. Wenn diese App einen gegenseitigen Schutz von Prozessen durchgeführt hat, wird die App häufig im Hintergrund neu gestartet. Wenn das Telefon die App installiert, an der Sie an der Entwicklung teilgenommen haben, konsumiert das Telefon über Nacht Strom und Verkehr, und Ihre App muss vom Benutzer deinstalliert oder schweigen.
Die Lösung hier ist:
Initialisieren Sie statische Mitglieder zu Beginn der Klasse nicht. Eine faule Initialisierung kann berücksichtigt werden.
Im architektonischen Design sollten wir darüber nachdenken, ob es wirklich notwendig ist, dies zu tun und zu vermeiden. Wenn die Architektur so gestaltet werden muss, haben Sie die Verantwortung, den Lebenszyklus dieses Objekts zu verwalten.
Vermeiden Sie Override Finalize ()
1. Die Abschlussmethode wird zu einem unsicheren Zeitpunkt ausgeführt und kann nicht darauf angewiesen werden, knappe Ressourcen freizugeben. Die Gründe für die unsichere Zeit sind:
Die Zeit, in der die virtuelle Maschine GC aufruft
Die Zeit, in der der Abschluss des Daemon -Threads geplant ist
2. Die Abschlussmethode wird nur einmal ausgeführt. Selbst wenn das Objekt wiederbelebt wird, wird es nicht erneut ausgeführt, wenn die Abschlussmethode ausgeführt wurde, wenn es erneut GC ist. Der Grund ist:
Das Objekt, das die Abschlussmethode enthält, generiert eine Abschlussreferenz durch die virtuelle Maschine, wenn sie neu sind, und Verweise auf das Objekt. Wenn die Abschlussmethode ausgeführt wird, wird die dem Objekt entsprechende Abschlussreferenz freigegeben. Selbst wenn das Objekt zu diesem Zeitpunkt wiederbelebt wird (dh das Verweisen auf das Objekt mit einer starken Referenz) und beim zweiten Mal GC, da die Abschlussreferenz nicht mehr entspricht, wird die Abschlussmethode nicht ausgeführt.
3. Ein Objekt, das die Abschlussmethode enthält, muss mindestens zwei GC -Runden durchlaufen, bevor sie freigegeben werden kann.
Speicherleck durch nicht abgestellte Ressource
对于使用了BraodcastReceiver,ContentObserver,File,游标Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
一些不良代码造成的内存压力
有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。
Zum Beispiel:
Bitmap 没调用recycle()方法,对于Bitmap 对象在不使用时,我们应该先调用recycle() 释放内存,然后才它设置为null. 因为加载Bitmap 对象的内存空间,一部分是java 的,一部分C 的(因为Bitmap 分配的底层是通过JNI 调用的)。 而这个recyle() 就是针对C 部分的内存释放。
构造Adapter 时,没有使用缓存的convertView ,每次都在创建新的converView。这里推荐使用ViewHolder。
Zusammenfassen
对Activity 等组件的引用应该控制在Activity 的生命周期之内; 如果不能就考虑使用getApplicationContext 或者getApplication,以避免Activity 被外部长生命周期的对象引用而泄露。
尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。
对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
将内部类改为静态内部类
静态内部类中使用弱引用来引用外部类的成员变量
Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空Handler 里面的消息。比如在Activity onStop 或者onDestroy 的时候,取消掉该Handler 对象的Message和Runnable.
在Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为null,比如使用完Bitmap 后先调用recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。
正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
The above is a summary of the causes of memory leaks in Java introduced to you by the editor and how to avoid memory leaks (super detailed version). Ich hoffe, es wird für alle hilfreich sein. Wenn Sie Fragen haben, hinterlassen Sie mir bitte eine Nachricht und der Editor wird Ihnen rechtzeitig antworten. Vielen Dank für Ihre Unterstützung auf der Wulin.com -Website!