In diesem Artikel werden die Prinzipien der Java -Speicherverwaltung und die Ursachen von Speicherlecks im Detail vorgelegt.
Java Memory Management Mechanismus
Wenn in C ++ ein Stück Speicher benötigt wird, um ein Stück Speicher dynamisch zuzuweisen, muss der Programmierer für den gesamten Lebenszyklus dieses Speichers verantwortlich sein. Von der Anwendung für die Allokation bis zur Verwendung bis zur endgültigen Version. Dieser Prozess ist sehr flexibel, aber sehr umständlich. Die Java -Sprache hat eine eigene Optimierung für die Speicherverwaltung vorgenommen, die der Mülleimermechanismus ist. Fast alle Speicherobjekte in Java werden im Heap -Speicher (mit Ausnahme grundlegender Datentypen) zugewiesen, und GC (Gage Collection) ist dann für den automatischen Recyclingspeicher verantwortlich, der nicht mehr verwendet wird.
Das obige ist die grundlegende Situation des Java -Speicherverwaltungsmechanismus. Wenn wir dies jedoch nur verstehen, werden wir in der tatsächlichen Projektentwicklung immer noch auf Speicherlecks stoßen. Einige Leute mögen bezweifeln, dass es immer noch Speicherlecks geben wird, da Javas Müllsammlungsmechanismus automatisch Speicher recyceln kann? In dieser Frage müssen wir wissen, wann GC Speicherobjekte recycelt und welche Art von Speicherobjekten von GC als "nicht mehr verwendet" betrachtet werden.
Zugriff auf Speicherobjekte in Java verwendet Referenzmethoden. Im Java -Code führen wir eine Referenzvariable eines Speicherobjekts durch den Wert dieser Referenzvariablen. In Java -Programmen kann diese Referenzvariable selbst im Heap -Speicher und im Speicher des Codestapels gespeichert werden (wie der grundlegende Datentyp). GC -Threads beginnen mit der Verfolgung von Referenzvariablen im Code -Stapel, um zu bestimmen, welcher Speicher verwendet wird. Wenn der GC -Thread ein Stück Heap -Speicher auf diese Weise nicht verfolgen kann, ist der GC der Ansicht, dass dieses Stück Speicher nicht mehr verwendet wird (da der Code nicht mehr auf dieses Stück Speicher zugreifen kann).
Wenn ein Speicherobjekt alle Referenzen verliert, kann GC es recyceln, wenn ein Speicherobjekt alle Referenzen verliert. Wenn das Objekt immer noch eine Referenz hat, wird es nicht von GC recycelt, auch wenn die virtuelle Java -Maschine vonMemoryError ausgibt.
Java -Speicherleck
Im Allgemeinen gibt es zwei Situationen für Speicherlecks. In einem Fall wird in der C/C ++ - Sprache alle zugewiesenen Speicher im Haufen gelöscht (z. B. Zeiger Neuzuweisung), wenn es nicht veröffentlicht wird. Speicher und seine Zugriffsmethode (Referenz). Der erste Fall ist, dass es in Java aufgrund der Einführung des Müllsammungsmechanismus gut gelöst wurde. Daher beziehen sich Speicherlecks in Java hauptsächlich auf den zweiten Fall.
Vielleicht ist es zu abstrakt, nur über das Konzept zu sprechen. Sie können sich ein solches Beispiel ansehen:
Die Codekopie lautet wie folgt:
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 gibt es Verweise auf das Vektorobjekt V und Verweise auf Objektobjekt O im Codestapel. In der für Schleife erzeugen wir kontinuierlich neue Objekte, fügen sie dann zum Vektorobjekt hinzu und leeren dann die O -Referenz. Die Frage ist, wenn GC nach leerem O -Referenz auftritt, wird das von GC erstellte Objektobjekt, das wir erstellen, recycelt? Die Antwort ist nein. Denn wenn GC Referenzen im Code -Stapel verfolgt, wird eine V -Referenz gefunden und weiterhin nachverfolgt, es wird festgestellt, dass ein Hinweis auf das Objektobjekt im Speicherraum vorhanden ist, auf das die V -Referenz verfolgt wird. Das heißt, obwohl die O -Referenz leer war, gibt es noch andere Referenzen im Objektobjekt und kann zugegriffen werden, sodass das GC sie nicht freigeben kann. Wenn das Objektobjekt nach dieser Schleife keinen Einfluss auf das Programm hat, sind wir der Meinung, dass in diesem Java -Programm ein Speicherleck aufgetreten ist.
Obwohl Java -Speicherlecks in C/C ++ weniger zerstörerisch für Speicherlecks sind, kann das Programm in den meisten Fällen mit Ausnahme einiger Fälle, in denen das Programm abstürzt, immer noch normal ausgeführt werden. Wenn jedoch mobile Geräte strenge Einschränkungen für Speicher und CPU aufweisen, führt der Java -Speicherüberlauf zu einer Ineffizienz von Programmen und der Belegung großer Mengen unerwünschter Speicher. Dies führt dazu, dass sich die Leistung der gesamten Maschine verschlechtert, und in schweren Fällen wird auch die Ausstimmung des MememoryErrors veranlasst, was zum Absturz des Programms führt.
Vermeiden Sie Speicherlecks im Allgemeinen
Im Allgemeinen, ohne komplexe Datenstrukturen einzubeziehen, zeigt sich Java -Speicherlecks, als der Lebenszyklus eines Speicherobjekts die Zeitdauer des Programms benötigt. Wir nennen es manchmal "Objektfrei".
Zum Beispiel:
Die Codekopie lautet wie folgt:
public class filesearch {
privates Byte [] Inhalt;
private Datei mFile;
public filearch (Dateidatei) {
mFile = Datei;
}
public boolean hassstring (String str) {
int size = getFileSize (mFile);
Content = neues Byte [Größe];
LoadFile (MFILE, Inhalt);
String s = neuer String (Inhalt);
return s.contains (str);
}
}
In diesem Code gibt es in der Dateisearch -Klasse eine Funktion, um festzustellen, ob das Dokument die angegebene Zeichenfolge enthält. Der Prozess soll MFILE zuerst in den Speicher laden und dann Urteile fällen. Das Problem hier ist jedoch, dass der Inhalt als Instanzvariable und nicht als lokale Variable deklariert wird. Nach der Rückgabe dieser Funktion existiert die Daten der gesamten Datei noch im Speicher. Es ist offensichtlich, dass wir diese Daten in Zukunft nicht mehr benötigen, was zu einer unangemessenen Gedächtnisverschwendung führt.
Um in diesem Fall Speicherlecks zu vermeiden, müssen wir unseren zugewiesenen Speicher mit C/C ++ Speichermanagement -Denken verwalten. Erstens soll der effektive Umfang des Speicherobjekts vor der Deklaration der Objektreferenz klären. Speicherobjekte, die innerhalb einer Funktion gültig sind, sollten als lokale Variablen deklariert werden, und diejenigen mit demselben Lebenszyklus wie die Klasseninstanz sollten als Instanzvariablen deklariert werden ... und so weiter. Denken Sie zweitens daran, das Speicherobjekt manuell zu entleeren, wenn es nicht mehr benötigt wird.
Speicher -Leck -Problem in komplexen Datenstrukturen
In tatsächlichen Projekten verwenden wir häufig einige komplexere Datenstrukturen, um die während des Programmvorgangs erforderlichen Dateninformationen zu zwischenstrahlen. Manchmal ist es für uns schwierig, mit den Daten umzugehen in der Datenstruktur. Zu diesem Zeitpunkt können wir einen speziellen Mechanismus in Java verwenden, um Speicherleckage zu verhindern.
Wir haben zuvor eingeführt, dass der GC -Mechanismus von Java auf dem Referenzmechanismus basiert, der das Gedächtnis verfolgt. Vorher haben die Referenzen, die wir verwendeten, nur eine Form von "Objekt O" definiert. Tatsächlich ist dies nur eine Standardsituation im Java -Referenzmechanismus, und es gibt zusätzlich einige andere Referenzmethoden. Durch die Verwendung dieser speziellen Referenzmechanismen und der Kombination mit dem GC -Mechanismus können wir einige der von uns benötigten Effekte erzielen.
Mehrere Referenzmethoden in Java
Es gibt verschiedene Arten des Zitierens von Java, nämlich: starkes Zitat, weicher Zitat, schwaches Zitat und virtuelles Zitat. Als nächstes verstehen wir zunächst die Bedeutung dieser Zitiermethoden im Detail.
Starkes Zitat
Die Zitate, die in den zuvor eingeführten Inhalten verwendet wurden, waren alle starken Zitate, die die häufigsten Zitate sind. Wenn ein Objekt eine starke Referenz hat, ähnelt es einer wesentlichen täglichen Notwendigkeit, und der Müllsammler wird es niemals recyceln. Wenn der Speicherraum nicht ausreicht, würde die virtuelle Java -Maschine einen Fehler aus der Mememory eher werfen, um das Programm abnormal zu beenden, als Objekte mit starken Verweise zu recyceln, um das Speicherproblem zu lösen.
Weiche
Eine typische Verwendung der Softrreferenzklasse ist für speicherempfindliche Caches. Das Prinzip der Weichweite besteht darin, sicherzustellen, dass alle weichen Referenzen vor dem JVM gelöscht werden. Der entscheidende Punkt ist, dass der Müllkollektor zur Laufzeit weiche zugängliche Objekte freisetzen kann (oder nicht). Ob ein Objekt befreit wird, hängt vom Algorithmus des Müllsammlers und der Menge an Speicher ab, wenn der Müllsammler ausgeführt wird.
Schwachreferenz
Eine typische Verwendung der WeaCreference -Klasse besteht darin, die Kartierung (kanonische Kartierung) zu normalisieren. Darüber hinaus sind schwache Referenzen auch für Objekte mit relativ langen Lebensdauern und einem Aufwand mit geringer Erholung nützlich. Der entscheidende Punkt ist, dass, wenn während des Müllsammlerlaufs ein schwach zugängliches Objekt auftritt, das von WeaCreference verwiesene Objekt freigegeben wird. Beachten Sie jedoch, dass der Müllsammler möglicherweise mehrmals laufen muss, bevor er schwach zugängliche Objekte finden und loslassen kann.
Phantomreferenz
Die PhantomReference -Klasse kann nur verwendet werden, um die kommenden Sammlungen referenzierter Objekte zu verfolgen. Ebenso kann es verwendet werden, um vor dem Mortem Clearing-Operationen auszuführen. Die Phantomreferenz muss mit der Referenzklasse verwendet werden. Referenzqueue ist erforderlich, da es als Benachrichtigungsmechanismus wirken kann. Wenn der Garbage Collector feststellt, dass ein Objekt ein virtuelles Zugriffsobjekt ist, wird das Phantomreferenzobjekt auf sein Referenzqueue platziert. Wenn Sie das Phantomreferenzobjekt in den Referenzqueue stellen, wird eine Benachrichtigung angezeigt, die darauf hinweist, dass das vom Phantomreferenzobjekt verwiesene Objekt beendet ist und zur Sammlung verfügbar ist. Auf diese Weise können Sie Maßnahmen ergreifen, bevor das vom Objekt besetzte Speicher recycelt wird. Referenz und Referenzqueue werden in Verbindung mit Referenzqueue verwendet.
GC, Referenz und Referenzqueue
A. GC kann den Speicher von Objekten nicht mit starken Referenzen löschen.
B. GC fand einen Objektspeicher mit nur weichen Referenzen, dann:
① Das Referenzfeld des Softreference -Objekts ist auf Null gesetzt, so dass das Objekt nicht mehr auf das Heap -Objekt bezieht.
② Das durch Sozidreferenz verwiesene Heap -Objekt wird als endgültig deklariert.
③ Wenn die Methode des HEAP -Objekts durch die Finalize () ausgeführt wird und der vom Objekt besetzte Speicher freigesetzt wird, wird das Softreference -Objekt zu seinem Referenzqueue hinzugefügt (falls der letztere existiert).
C. GC entdeckt einen Objektspeicher mit nur schwachen Referenzen, dann:
① Das Referenzfeld des WewReference -Objekts wird auf Null gesetzt, so dass sich das Objekt nicht mehr auf das Heap -Objekt bezieht.
② Das von WewReference verwiesene Heap -Objekt wird als endgültig deklariert.
③ Wenn die Methode Finalize () des Heap -Objekts ausgeführt wird und der vom Objekt besetzte Speicher freigegeben wird, wird dem Referenz -Referenz -Objekt (falls der letztere existiert).
D. GC entdeckt ein Objektspeicher, das nur virtuelle Referenzen hat, dann:
① Das durch Phantomreferenz verwiesene Heap -Objekt wird als endgültig erklärt.
② PhantomReference wird zu seinem Referenzqueue hinzugefügt, bevor das Heap -Objekt freigegeben wird.
Die folgenden Punkte sind erwähnenswert:
1. GC findet im Allgemeinen keine weichbezogenen Speicherobjekte.
2. Die Entdeckung und Freisetzung von schwachen Referenzen ist nicht sofort.
3. Wenn weiche Referenzen und schwache Referenzen zu Referenzqueue hinzugefügt werden, wurden die Referenzen auf den realen Speicher auf leer eingestellt und der entsprechende Speicher wurde veröffentlicht. Beim Hinzufügen einer virtuellen Referenz zu Referenzqueue wurde der Speicher noch nicht freigegeben und kann weiterhin zugegriffen werden.
Durch die obige Einführung glaube ich, dass Sie ein gewisses Verständnis des Java -Zitiermechanismus und der Ähnlichkeiten und Unterschiede mehrerer Zitiermethoden haben. Nur ein Konzept ist zu abstrakt.
Die Codekopie lautet wie folgt:
String Str = New String ("Hallo");
Referenzqueue <string> rq = new ReferenceQueue <String> ();
Weapreference <String> wf = new Wecreference <string> (STR, RQ);
STR = NULL;
String str1 = wf.get ();
// Wenn das "Hallo" -Objekt nicht recycelt ist, gibt rq.poll () null zurück
Referenz <?
Achten Sie im obigen Code auf die beiden Orte ⑤⑥. Wenn das "Hallo" -Objekt nicht recycelt wird wf.get () gibt das String -Objekt "Hallo" zurück, rq.poll () wird null zurückgegeben; null, rq.poll () gibt ein Referenzobjekt zurück, es gibt jedoch in diesem Referenzobjekt keine Referenz auf das STR -Objekt (PhantomReference unterscheidet sich von Schwachempfehlungen und Sozidien).
Gemeinsame Anwendung von Zitiermechanismus und komplexen Datenstrukturen
Durch das Verständnis des GC -Mechanismus, des Referenzmechanismus und der Kombination mit Referenzqueue können wir einige komplexe Datentypen implementieren, die den Speicherüberlauf verhindern.
Zum Beispiel hat SofTreference die Eigenschaften des Aufbaus eines Cache -Systems, sodass wir ein einfaches Cache -System in Kombination mit Hash -Tabellen implementieren können. Dies stellt nicht nur sicher, dass so viele Informationen zwischengespeichert werden können, sondern stellt auch sicher, dass die virtuelle Java -Maschine aufgrund von Speicherverlust nicht herausgeworfen wird. Dieser Caching -Mechanismus ist besonders für Situationen geeignet, in denen Speicherobjekte einen langen Lebenszyklus haben und die Zeit für die Generierung von Speicherobjekten relativ lang ist, z. B. Cover -Bilder von Cache -Listen usw. In einigen Fällen, in denen der Lebenszyklus lang ist, aber der Overhead der Erzeugung von Speicherobjekten nicht groß ist, kann die Verwendung von WeaCreference eine bessere Speicherverwaltung erzielen.
Eine Kopie von SofthashMaps Quellcode ist beigefügt.
Die Codekopie lautet wie folgt:
Paket com.
//: SofthashMap.java
Java.util importieren.
importieren java.lang.ref.
import Android.util.log;
öffentliche Klasse SofthashMap erweitert AbstractMap {
/** Die interne Hashmap, die die Weiche hält.
private endgültige Karte Hash = new Hashmap ();
/** Die Anzahl der "harten" Referenzen, die intern gehalten werden sollen.
private endgültige int hard_size;
/** Die FIFO -Liste der harten Referenzen, Reihenfolge des letzten Zugriffs.
private final linkedList hardcache = new LinkedList ();
/** Referenzwarteschlange für gelöschte Softrreferenzobjekte.
private referencequeue queue = new referencequeue ();
// starke Referenznummer
public sowthashMap () {this (100);
public sowthashMap (int hardsize) {hard_size = hardsize;
öffentliches Objekt get (Objektschlüssel) {
Objektergebnis = null;
// Wir erhalten die von diesem Schlüssel dargestellte Sozidienferenz
Softreference Soft_ref = (SofTreference) Hash.get (Schlüssel);
if (Soft_ref! = null) {
// Aus der Sozidreferenz erhalten wir den Wert, der sein kann
// null Wenn es nicht in der Karte war oder es entfernt wurde
// Die unten definierte ProcessQueue () -Methode
result = Soft_ref.get ();
if (result == null) {
// Wenn der Wert Müll gesammelt wurde, entfernen Sie die
// Eintrag aus dem HashMap.
Hash.Remove (Schlüssel);
} anders {
// Wir fügen dieses Objekt jetzt zum Beginn des Hards hinzu
// Referenzwarteschlange.
// einmal, weil die Suche der FIFO -Warteschlange so langsam sind, also
// Wir wollen es nicht jedes Mal durchsuchen, um zu entfernen
// Duplikate.
// Halten Sie das aktuelle Verwenden von Objekt im Speicher ein
Hardcache.addfirst (Ergebnis);
if (hardcache.size ()> hard_size) {
// Entfernen Sie den letzten Eintrag, wenn Sie länger als Hard_size auflisten,
Hardcache.removelast ();
}
}
}
Rückgabeergebnis;
}
/** Wir definieren unsere eigene Unterklasse von Softrreference, die enthält
Nicht nur der Wert, sondern auch der Schlüssel, um es einfacher zu finden
Der Eintrag in der Hashmap wurde nach Müll gesammelt.
Private statische Klasse SoftValue erweitert Softrreference {
privates endgültiges Objektschlüssel;
/** Wussten Sie, dass eine äußere Klasse auf private Daten zugreifen kann
Mitglieder und Methoden einer inneren Klasse?
Ich dachte, es war nur die innere Klasse, die auf die zugreifen konnte
Die privaten Informationen der äußeren Klasse.
Zugang zu privaten Mitgliedern einer inneren Klasse in der inneren
Klasse. */
Private SoftValue (Objekt K, Objektschlüssel, Referenzqueue q) {
Super (k, q);
this .Key = key;
}
}
/** Hier gehen wir durch den Referenzqueue und entfernen Müll
gesammelte SoftValue -Objekte aus der Hashmap, indem Sie sie nachsehen
Verwenden Sie das SoftValue.Key -Datenmitglied.
public void processQueue () {
SoftValue SV;
while ((sv = (SoftValue) queue.poll ())! = null) {
if (sv.get () == null) {
Log.e ("processQueue", "null");
} anders {
Log.e ("processQueue", "nicht null");
}
Hash.Remove (Sv.Key); // Wir können auf private Daten zugreifen!
Log.e ("SofthashMap", "Release" + Sv.Key);
}
}
/** Hier setzen wir das Wert -Wert -Paar in die HashMap mit Verwendung
ein SoftValue -Objekt.
öffentliches Objekt Put (Objektschlüssel, Objektwert) {
processQueue ();
Log.e ("SofthashMap", "in" + taste);
Return Hash.put (Schlüssel, neuer SoftValue (Wert, Schlüssel, Warteschlange));
}
öffentliches Objekt entfernen (Objektschlüssel) {
processQueue ();
return hash.remove (Schlüssel);
}
public void clear () {
Hardcache.clear ();
processQueue ();
Hash.Clear ();
}
public int size () {
processQueue ();
return hash.size ();
}
public set einstieg () {
// Nein, nein, du kannst das nicht tun !!!
neue nicht unterstützte OperationException () werfen;
}
}