Ein wichtiges Merkmal von Java besteht darin, das Speicherrecycling automatisch durch einen Müllsammler (GC) zu verwalten, ohne die Programmierer zu verpflichten, das Speicher selbst freizugeben. Theoretisch kann alle Speicher, die von Objekten besetzt sind, die nicht mehr in Java verwendet werden, von GC recycelt werden, aber Java hat auch Speicherlecks, aber seine Leistung unterscheidet sich von C ++.
Speicherverwaltung in Java
Um Speicherlecks in Java zu verstehen, müssen Sie zunächst wissen, wie Speicher in Java verwaltet wird.
In Java -Programmen verwenden wir in der Regel neue, um Objekten Speicher zuzuweisen, und diese Speicherplätze befinden sich auf dem Haufen (Heap).
Hier ist ein Beispiel:
public class Simple {public static void main (String args []) {Object Object1 = new Object (); // obj1 Object Object2 = new Object (); // obj2 Object2 = Object1; //...At diesmal kann OBJ2 gereinigt werden}}}Java verwendet gerichtete Grafiken für die Speicherverwaltung:
In der angegebenen Grafik nennen wir OBJ1 als zugänglich, OBJ2 als unerreichbar und offensichtlich nicht erreichbar.
Die Veröffentlichung des Speichers, dh die Reinigung unerreichbarer Objekte, wird von GC bestimmt und ausgeführt, sodass GC den Status jedes Objekts, einschließlich Anwendung, Zitat, Zitat und Zuordnung, überwacht. Das Grundprinzip der Veröffentlichung eines Objekts ist, dass das Objekt nicht mehr verwendet wird:
Das Objekt erhält einen Nullwert und wurde nie wieder aufgerufen.
Das andere besteht darin, dem Objekt einen neuen Wert zuzuweisen, der den Speicherplatz umgeht.
Normalerweise wird angenommen, dass die Kosten für die Zuweisung von Objekten auf dem Haufen relativ hoch sind, aber GC optimiert diesen Vorgang: In C ++ sucht die Zuordnung eines Speichers auf dem Haufen nach einem geeigneten Speicher, der zur Zuweisung zugewiesen wird. Wenn das Objekt zerstört wird, kann dieses Stück Erinnerung wiederverwendet werden; In Java möchten Sie einen langen Streifen. Jedes Mal, wenn ein neues Objekt zugewiesen wird, wechselt Javas "Heap -Zeiger" rückwärts in einen Bereich, der nicht zugewiesen wurde. Daher ist die Effizienz des Java -Zuweisung des Gedächtnisses vergleichbar mit der von C ++.
Bei dieser Arbeitsweise gibt es jedoch ein Problem: Wenn häufig Speicher angewendet wird, werden die Ressourcen erschöpft. Zu diesem Zeitpunkt greift GC ein, erholt sich den Raum und macht die Objekte im Haufen kompakter. Auf diese Weise wird es immer genügend Speicherplatz geben, um sie zuzuweisen.
Referenzzählmethode Wenn GC -Reinigung: Wenn eine Referenz mit einem neuen Objekt verbunden ist, beträgt die Referenzzahl +1; Wenn eine Referenz den Bereich verlässt oder auf Null gesetzt ist, beträgt die Referenzzahl -1. Wenn GC feststellt, dass diese Zählung 0 ist, recycelt sie den von ihm verbrauchten Speicher. Dieser Overhead tritt während der gesamten Lebensdauer des Referenzprogramms auf und kann keine kreisförmigen Referenzen bewältigen. Diese Methode wird also nur verwendet, um zu veranschaulichen, wie GC funktioniert, und wird von keiner java -virtuellen Maschine angewendet.
Die meisten GCs verwenden eine adaptive Reinigungsmethode (plus andere zusätzliche Techniken für die Geschwindigkeitsanhingung), hauptsächlich auf der Basis von "Live-Objekten" und dann auf der Verwendung eines "adaptiven, generationsübergreifenden, stop-kopischen, markierten" Müllsammlers. Ich werde nicht zu viel vorstellen, dies steht nicht im Mittelpunkt dieses Artikels.
Speicherleck in Java
Speicherverlust in Java, weitgehend und in Laienbegriffen, ist, dass die Erinnerung an Objekte, die nicht mehr verwendet werden, nicht mehr recycelt werden kann, was eine Speicherverletzung ist.
Speicherlecks in Java unterscheiden sich von denen in C ++.
In C ++ müssen alle Objekte, denen der Speicher zugewiesen wurde, vom Programmierer manuell freigegeben werden, nachdem sie nicht mehr verwendet wurden. Daher enthält jede Klasse einen Destruktor, der die Reinigungsarbeiten ausfüllen soll. Wenn wir vergessen, bestimmte Objekte freizugeben, wird ein Speicherleckage verursacht.
Aber in Java müssen wir den Gedächtnis nicht selbst freisetzen, und nutzlose Objekte werden automatisch von GC gereinigt, was unsere Programmierarbeiten erheblich vereinfacht. Manchmal können jedoch einige Objekte, die nicht mehr verwendet werden, nicht mehr im GC freigesetzt werden, was Speicherverletzung verursacht.
Wir wissen, dass Objekte Lebenszyklen haben, einige lange und einige kurz. Wenn Objekte für lange Lebenszyklus Referenzen mit kurzen Lebenszyklen enthalten, ist wahrscheinlich das Leckage des Gedächtnisses. Lassen Sie uns ein einfaches Beispiel geben:
öffentliche Klasse einfach {Objektobjekt; public void method1 () {Object = new Object (); //...Other Code}}Tatsächlich erwarten wir, dass es nur in der Methode1 () -Methode verwendet wird und nicht an anderer Stelle verwendet wird. Wenn jedoch die Methode1 () -Methode ausgeführt wird, wird der vom Objektobjekt zugewiesene Speicher nicht sofort als ein Objekt angesehen, das freigegeben werden kann, und wird erst nach dem von der einfachen Klasse erstellten Objekt freigegeben. Streng genommen ist dies ein Speicherleck. Die Lösung besteht darin, Objekt als lokale Variable in der Methode1 () -Methode zu verwenden. Wenn Sie dies schreiben müssen, können Sie es natürlich in dies ändern:
öffentliche Klasse einfach {Objektobjekt; public void method1 () {Object = new Object (); //...Other Code Object = null; }}Auf diese Weise kann der von "NewObject ()" zugewiesene Speicher von GC recycelt werden.
Zu diesem Zeitpunkt sollten Java -Speicherlecks klarer sein. Erklären wir weiter unten:
Wenn der zugewiesene Speicher im Heap nicht freigegeben wird, werden alle Möglichkeiten zum Zugriff auf diesen Speicher gelöscht (z. B. Zeiger Neuzuweisung). Dies ist für Sprachen wie C ++. Die GC in Java hilft uns, diese Situation zu bewältigen, sodass wir uns nicht darum kümmern müssen.
Wenn das Speicherobjekt offensichtlich nicht benötigt wird, behält es diesen Speicher und seine Zugriffsmethode (Referenz) bei, die in allen Sprachen auftreten kann. Wenn Sie beim Programmieren nicht vorsichtig sind, ist dies einfach zu passieren, und wenn es nicht zu ernst ist, ist es möglicherweise nur ein kurzes Speicherleck.
Einige Beispiele und Lösungen, die anfällig für Speicherleckage sind
Die Situation wie das obige Beispiel ist leicht zu passieren, und es ist auch die wahrscheinlichste Situation, die wir ignorieren und die Speicherlecks verursachen. Die Lösung besteht darin, den Umfang des Objekts zu minimieren (z. B. in Androidstudio, der obige Code wird eine Warnung ausgeben, und die angegebenen Vorschläge sollen die Mitgliedsvariablen der Klasse in lokale Variablen in der Methode umschreiben) und den Nullwert manuell festlegen.
Was den Umfang betrifft, müssen wir beim Schreiben von Code mehr Aufmerksamkeit schenken. Wenn wir den Nullwert manuell festlegen, können wir uns die interne Methode zum Löschen des angegebenen Knotens im LinkedList -Quellcode Java Container ansehen (siehe: Javas LinkedList -Quellcode -Interpretation (JDK1.8) ):
// Löschen Sie den angegebenen Knoten und geben Sie den gelöschten Element -Wert E Unlink zurück (Knoten <e> x) {// den aktuellen Wert und die vorderen und hinteren Knoten endgültig E -Element = X.Item; endgültiger Knoten <e> next = x.Next; endgültiger Knoten <e> prew = x.prev; if (prev == null) {first = next; // Wenn der vorherige Knoten leer ist (z. B. der aktuelle Knoten ist der erste Knoten), wird der nächste Knoten zum neuen ersten Knoten} else {prev.Next = next; // Wenn der vorherige Knoten nicht leer ist, weist er auf den aktuellen nächsten Knoten x.prev = null hin. } if (next == null) {last = prev; // Wenn der nächste Knoten leer ist (z. B. der aktuelle Knoten ist ein Heckknoten), wird der vorherige des aktuellen Knotens zum neuen Heckknoten} else {next.prev = prev; // Wenn der nächste Knoten nicht leer ist, zeigt der nächste Knoten auf den aktuellen vorherigen Knoten x.next = null; } X.Item = null; Größe--; ModCount ++; Rückgabeelement; }Zusätzlich zur Änderung der Beziehung zwischen Knoten müssen wir Null auch einen Wert zuweisen. Unabhängig davon, wann GC mit der Reinigung beginnt, sollten wir nutzlose Objekte rechtzeitig als saubere Objekte markieren.
Wir wissen, dass die Java -ContainerarrayList in einem Array implementiert ist (siehe: Javas ArrayList -Quellcode -Interpretation (JDK1.8) ). Wenn wir dafür eine Pop () () -Methode schreiben möchten, könnte dies so aussehen:
public e pop () {if (size == 0) return null; sonst return (e) elementData [-Größe]; }Die Schreibmethode ist sehr präzise, führt jedoch hier Speicherüberlauf: ElementData [Size-1] hat immer noch einen Verweis auf ein E-Typ-Objekt und kann vorerst nicht von GC recycelt werden. Wir können es wie folgt ändern:
public e pop () {if (size == 0) return null; sonst {e e = (e) elementData [-Größe]; ElementData [Größe] = NULL; Rückkehr e; }}Beim Schreiben von Code können wir die Einfachheit nicht blind verfolgen. Das erste ist, seine Genauigkeit zu gewährleisten.
Speicherlecks während des Containergebrauchs
In vielen Artikeln sehen Sie möglicherweise ein Beispiel für Speicherlecks wie folgt:
Vektor v = neuer Vektor (); für (int i = 1; i <100; i ++) {Object o = new Object (); v.Add (o); o = null; }Viele Menschen verstehen es am Anfang möglicherweise nicht, sodass wir den obigen Code wie folgt verstehen können:
void method () {vector vector = new vector (); für (int i = 1; i <100; i ++) {Object Object = new Object (); vector.add (Objekt); Objekt = NULL; } //...Operationen auf Vektor // ... Andere Operationen, die nicht mit Vector zusammenhängen}}Hier beziehen sich Speicherlecks auf die Tatsache, dass diese Reihe von Objekten nach Abschluss des Vektorbetriebs nicht recycelt werden kann. Das Speicherleck hier kann von kurzer Dauer sein, da diese Objekte nach der gesamten Methode () -Methode weiterhin recycelt werden können. Hier ist es sehr einfach zu lösen, nur manuell dem Wert null zuweisen:
void method () {vector vector = new vector (); für (int i = 1; i <100; i ++) {Object Object = new Object (); vector.add (Objekt); Objekt = NULL; } //...Operationen auf v vector = null; //...OTHER OPERATIONEN, die nicht mit V} zusammenhängenDer obige Vektor ist veraltet, aber nur eine Einführung in Speicherlecks mit alten Beispielen. Wenn wir Container verwenden, ist es einfach, wie das obige Beispiel Speicherleckage zu verursachen. Im obigen Beispiel ist jedoch der durch lokale Variablen in der Methode während der Containerzeit verursachte Speicherleck -Effekt möglicherweise nicht sehr groß (wir sollten es aber auch vermeiden). Wenn dieser Container jedoch eine Mitgliedsvariable einer Klasse oder sogar eine statische Mitgliedsvariable ist, sollten Sie mehr Aufmerksamkeit auf Speicherleckage achten.
Das Folgende ist ein Fehler, der bei der Verwendung von Containern auftreten kann:
public class collectionMemory {public static void main (String s []) {set <MyObject> Objects = new LinkedHashSet <MyObject> (); Objects.Add (New MyObject ()); Objects.Add (New MyObject ()); Objects.Add (New MyObject ()); Objects.Add (New MyObject ()); System.out.println (Objects.Size ()); while (true) {Objects.add (new myObject ()); }}} Klasse MyObject {// Setzen Sie die Standardarray -Länge auf 99999 und erfolgen schneller outofMemoryErrorliste <string> list = new ArrayList <> (99999);}Das Ausführen des obigen Codes meldet einen Fehler sehr schnell:
3Exception in Thread "Haupt" java.lang.outofMemoryError: Java Heap Space bei Java.util.ArrayList. com.Anxpp.memory.collectionMemory.main (CollectionMemory.java:16)
Wenn Sie genug über Java -Container wissen, tritt der obige Fehler nicht auf. Hier ist auch ein Beitrag, in dem ich Java -Container vorstelle: ...
Der Containersatz speichert nur einzigartige Elemente und wird über die Equals () -Methode des Objekts verglichen. Alle Klassen in Java werden jedoch direkt oder indirekt an die Objektklasse geerbt. Die Equals () -Methode der Objektklasse vergleicht die Adresse des Objekts. Im obigen Beispiel werden Elemente hinzugefügt, bis der Speicherüberlauf ist.
Daher ist das obige Beispiel streng genommen, Speicherüberlauf, der durch die falsche Verwendung des Containers verursacht wird.
In Bezug auf die festgelegte Methode verwendet die Methode von REME () () auch die Equals () -Methode zum Löschen von Matching -Elementen. Wenn ein Objekt die korrekte Equals () -Methode bereitstellt, denken Sie daran, dass Sie nach Änderung dieses Objekts nicht entfernen (Objekto) verwenden, dies kann auch Speicherleckage verursachen.
Verschiedene Objekte, die eine Close () -Methode liefern
Beispielsweise werden bei Verwendung von Datenbankverbindungen (DataSourse.getConnection ()), Netzwerkverbindungen (Socket) und IO -Verbindungen und bei Verwendung anderer Frameworks nicht automatisch von GC recycelt, es sei denn, sie rufen ihre Methode (oder eine ähnliche Methode) ausdrücklich auf, um ihre Verbindung zu schließen. Tatsächlich ist der Grund, dass Objekte für Langzeit-Zyklus auf Kurzzeit-Zyklusobjekte verweisen.
Viele Menschen haben möglicherweise Hibernate verwendet. Wenn wir die Datenbank betreiben, erhalten wir eine Sitzung über die SessionFactory:
Session Session = SessionFactory.openSession ();
Nach Abschluss müssen wir die Close () -Methode zum Schließen aufrufen:
Sitzung.CLOSE ();
SessionFactory ist ein langes Objekt und Sitzung ist ein relativ kurzes Objekt, aber das Framework ist auf diese Weise gestaltet: Es weiß nicht, wie lange wir die Sitzung verwenden, sodass wir nur eine Möglichkeit bieten können, zu entscheiden, wann wir es nicht mehr verwenden.
Da eine Ausnahme vor der Close () -Methode aufgerufen wird, kann die Methode nicht aufgerufen werden. Wir verwenden normalerweise die Try -Sprache und führen dann schließlich Close () und andere Reinigungsarbeiten in der Erklärung aus:
try {session = sessionFactory.openSession (); //...Other Operations} endlich {session.close (); }Speicherlecks im Singleton -Modus verursacht
In vielen Fällen können wir seinen Lebenszyklus als den Lebenszyklus des gesamten Programms ähnlich betrachten, daher ist es ein Objekt mit einem langen Lebenszyklus. Wenn dieses Objekt Verweise auf andere Objekte enthält, sind auch Speicherlecks anfällig für eintreten.
Verweise auf interne Klassen und externe Module
Tatsächlich ist das Prinzip immer noch das gleiche, aber die Art und Weise, wie es erscheint, ist anders.
Methoden im Zusammenhang mit der Reinigung
In diesem Abschnitt geht es hauptsächlich um GC () und Finalize () -Methoden.
GC ()
Für Programmierer ist GC im Grunde transparent und unsichtbar. Die Funktion, die GC ausführt, lautet system.gc (), und nach dem Aufrufen startet sie den Müllsammler und beginnt zu reinigen.
Nach der Definition der Java -Sprachspezifikation garantiert diese Funktion jedoch nicht, dass der Müllsammler der 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 Hotspotjvm unterstützt diese Funktion.
finalize ()
Finalize () ist eine Methode in der Objektklasse.
Diejenigen, die C ++ kennen, wissen, dass es einen Destruktor gibt, aber es ist keineswegs gleich dem Destruktor in C ++.
Dies wird in der Java -Programmierungsidee erläutert: Sobald GC bereit ist, den vom Objekt besetzten Speicherplatz zu befreien, wird seine Finalize () -Methode zuerst aufgerufen, und der vom Objekt besetzte Speicher wird nur dann recycelt, wenn die nächste GC -Recyclingaktion auftritt, sodass wir einige Reinigungsarbeiten in Finalise () einsetzen können.
Ein wichtiger Zweck dieser Methode ist: Beim Aufrufen von Nicht-Java-Code (z. B. C und C ++) in Java können entsprechende Funktionen für Speicherspeicher (z. B. MALOC () -Funktion von c (wie Cs malloc ()) in diesen Nicht-Java-Codes verwendet werden. In diesen Nicht-Java-Codes werden diese Speicher nicht freigegeben.
Daher ist Finalize () nicht für die normale Reinigung geeignet.
Manchmal hat diese Methode jedoch einige Verwendungen:
Wenn es eine Reihe von Objekten gibt, hat eines der Objekte einen falschen Zustand. Wenn wir dieses Objekt verarbeitet haben, wird der Staat wahr. Um fehlende Objekte ohne Verarbeitung zu vermeiden, können Sie die Methode Finalize () verwenden:
Klasse myObject {boolean State = false; public void Deal () {//...Some Processing Operations State = true; } @Override protected void endfinalize () {if (! State) {System.out.println ("Fehler:" + "Objekt nicht verarbeitet!"); }} // ...}Aus vielen Aspekten wird jedoch empfohlen, dass diese Methode nicht verwendet und als redundant angesehen wird.
Im Allgemeinen werden Speicherlecks durch schlechte Codierung verursacht. Wir können die JVM nicht dafür verantwortlich machen, dass wir nicht angemessener aufgeräumt werden.
Zusammenfassen
Das obige ist alle detaillierte Erläuterungen des von Speicher durchgesickerten Code in der Java -Sprache. Ich hoffe, es wird für alle hilfreich sein. Interessierte Freunde können weiterhin auf andere verwandte Themen auf dieser Website verweisen. Wenn es Mängel gibt, hinterlassen Sie bitte eine Nachricht, um darauf hinzuweisen. Vielen Dank an Freunde für Ihre Unterstützung für diese Seite!