Vor nicht allzu langer Zeit habe ich einige Kandidaten interviewt, die auf der Suche nach einem Job als leitender Java-Entwicklungsingenieur waren. Ich interviewe sie oft und frage: „Können Sie mir einige schwache Referenzen in Java vorstellen?“ Wenn der Interviewer fragt: „Hat das etwas mit der Speicherbereinigung zu tun?“, bin ich im Grunde zufrieden und werde es nicht tun Erwarten Sie nicht, dass die Antwort eine Beschreibung einer Arbeit ist, die den Dingen auf den Grund geht.
Allerdings war ich wider Erwarten überrascht, dass von den knapp 20 Kandidaten mit durchschnittlich 5 Jahren Entwicklungserfahrung und hochgebildetem Hintergrund nur zwei Personen von der Existenz schwacher Referenzen wussten, aber nur einer dieser beiden Personen wirklich Erfahren Sie mehr darüber. Während des Interviewprozesses habe ich auch versucht, einige Dinge anzustoßen, um zu sehen, ob jemand plötzlich sagen würde: „Das ist es“, aber das Ergebnis war sehr enttäuschend. Ich begann mich zu fragen, warum dieses Wissen so ignoriert wurde. Schließlich sind schwache Referenzen eine sehr nützliche Funktion, und diese Funktion wurde mit der Veröffentlichung von Java 1.2 vor 7 Jahren eingeführt.
Nun, ich erwarte nicht, dass Sie nach der Lektüre dieses Artikels ein Experte für schwache Referenzen werden, aber ich denke, Sie sollten zumindest verstehen, was schwache Referenzen sind, wie man sie verwendet und in welchen Szenarien sie verwendet werden. Da es sich um unbekannte Konzepte handelt, werde ich die vorherigen drei Fragen kurz erläutern.
Starke Referenz
Eine starke Referenz ist die Referenz, die wir häufig verwenden. Sie lautet wie folgt:
Kopieren Sie den Codecode wie folgt:
StringBuffer buffer = new StringBuffer();
Das Obige erstellt ein StringBuffer-Objekt und speichert einen (starken) Verweis auf dieses Objekt im Variablenpuffer. Ja, es handelt sich um eine Kinderoperation (bitte verzeihen Sie mir, wenn ich das sage). Das Wichtigste an einer starken Referenz ist, dass sie die Referenz stark machen kann, was ihre Interaktion mit dem Garbage Collector bestimmt. Insbesondere wenn ein Objekt über eine Reihe stark referenzierter Links erreichbar ist (stark erreichbar), wird es nicht recycelt. Das ist genau das, was Sie brauchen, wenn Sie nicht möchten, dass der Gegenstand, mit dem Sie arbeiten, recycelt wird.
Aber starke Zitate sind so stark
In einem Programm ist es etwas ungewöhnlich, eine Klasse als nicht erweiterbar festzulegen. Dies kann natürlich erreicht werden, indem die Klasse als endgültig markiert wird. Oder es kann komplizierter sein, nämlich eine Schnittstelle (Interface) über eine Factory-Methode zurückzugeben, die eine unbekannte Anzahl spezifischer Implementierungen enthält. Wir möchten beispielsweise eine Klasse namens Widget verwenden, diese Klasse kann jedoch nicht vererbt werden, sodass keine neuen Funktionen hinzugefügt werden können.
Aber was sollen wir tun, wenn wir zusätzliche Informationen über das Widget-Objekt verfolgen möchten? Angenommen, wir müssen die Seriennummer jedes Objekts aufzeichnen, aber da die Widget-Klasse dieses Attribut nicht enthält und nicht erweitert werden kann, können wir dieses Attribut nicht hinzufügen. Tatsächlich gibt es überhaupt kein Problem. HashMap kann die oben genannten Probleme vollständig lösen.
Kopieren Sie den Codecode wie folgt:
serialNumberMap.put(widget, widgetSerialNumber);
Dies mag auf den ersten Blick in Ordnung erscheinen, aber starke Verweise auf Widget-Objekte können Probleme verursachen. Wir können sicher sein, dass wir den Eintrag aus der Karte entfernen sollten, wenn die Seriennummer eines Widgets nicht mehr benötigt wird. Wenn wir es nicht entfernen, kann es zu Speicherverlusten kommen oder wir löschen die von uns verwendeten Widgets, wenn wir sie manuell entfernen, was zum Verlust gültiger Daten führen kann. Tatsächlich sind diese Probleme sehr ähnlich. Dies ist ein Problem, auf das Sprachen ohne Garbage-Collection-Mechanismus häufig bei der Speicherverwaltung stoßen. Aber wir müssen uns über dieses Problem keine Sorgen machen, da wir die Java-Sprache mit einem Garbage-Collection-Mechanismus verwenden.
Ein weiteres Problem, das starke Referenzen verursachen können, ist das Caching, insbesondere bei großen Dateien wie Bildern. Angenommen, Sie haben ein Programm, das von Benutzern bereitgestellte Bilder verarbeiten muss. Ein gängiger Ansatz besteht darin, Bilddaten zwischenzuspeichern, da das Laden von Bildern von der Festplatte sehr teuer ist und wir gleichzeitig vermeiden möchten, zwei Kopien derselben Bilddaten zu haben gleichzeitig im Gedächtnis.
Der Zweck des Caching besteht darin, zu verhindern, dass wir unnötige Dateien erneut laden. Sie werden schnell feststellen, dass der Cache immer einen Verweis auf die Bilddaten im Speicher enthält. Die Verwendung starker Referenzen führt dazu, dass die Bilddaten im Speicher verbleiben. Dies erfordert, dass Sie entscheiden, wann die Bilddaten nicht mehr benötigt werden, und sie manuell aus dem Cache entfernen, damit sie vom Garbage Collector wiederhergestellt werden können. Sie sind also erneut gezwungen, das zu tun, was der Garbage Collector tut, und manuell zu entscheiden, welche Objekte bereinigt werden sollen.
Schwache Referenz
Eine schwache Referenz ist einfach eine Referenz, die nicht so stark darin ist, das Objekt im Gedächtnis zu behalten. Mit WeakReference hilft Ihnen der Garbage Collector bei der Entscheidung, wann das referenzierte Objekt recycelt wird, und beim Entfernen des Objekts aus dem Speicher. Erstellen Sie eine schwache Referenz wie folgt:
Kopieren Sie den Codecode wie folgt:
eakReference<Widget> schwachWidget = new WeakReference<Widget>(widget);
Sie können das echte Widget-Objekt mithilfe von schwachWidget.get() abrufen. Da schwache Referenzen den Garbage Collector nicht daran hindern können, sie zu recyceln, werden Sie feststellen, dass bei der Verwendung plötzlich null zurückgegeben wird (wenn keine starke Referenz auf das Widget-Objekt vorhanden ist). erhalten.
Der einfachste Weg, das obige Problem der Widget-Sequenznummernaufzeichnung zu lösen, ist die Verwendung der in Java integrierten WeakHashMap-Klasse. WeakHashMap ist fast dasselbe wie HashMap, der einzige Unterschied besteht darin, dass seine Schlüssel (keine Werte!!!) von WeakReference referenziert werden. Wenn ein WeakHashMap-Schlüssel als Müll markiert wird, wird der diesem Schlüssel entsprechende Eintrag automatisch entfernt. Dadurch wird das oben genannte Problem des manuellen Löschens unnötiger Widget-Objekte vermieden. WeakHashMap kann problemlos in HashMap oder Map konvertiert werden.
Referenzwarteschlange
Sobald ein schwaches Referenzobjekt null zurückgibt, wird das Objekt, auf das die schwache Referenz zeigt, als Müll markiert. Und dieses schwache Referenzobjekt (nicht das Objekt, auf das es zeigt) ist nutzlos. Normalerweise müssen zu diesem Zeitpunkt einige Aufräumarbeiten durchgeführt werden. Beispielsweise entfernt WeakHashMap zu diesem Zeitpunkt nutzlose Einträge, um zu vermeiden, dass bedeutungslose schwache Referenzen gespeichert werden, die auf unbestimmte Zeit wachsen.
Referenzwarteschlangen erleichtern die Verfolgung unerwünschter Referenzen. Wenn Sie beim Erstellen von WeakReference ein ReferenceQueue-Objekt übergeben und das Objekt, auf das die Referenz zeigt, als Müll markiert ist, wird das Referenzobjekt automatisch zur Referenzwarteschlange hinzugefügt. Als Nächstes können Sie die eingehende Referenzwarteschlange in einem festgelegten Zeitraum verarbeiten, indem Sie beispielsweise einige Aufräumarbeiten durchführen, um mit diesen nutzlosen Referenzobjekten umzugehen.
Vier Arten von Referenzen
In Java gibt es tatsächlich vier Arten von Referenzen mit unterschiedlichen Stärken: Von stark bis schwach: starke Referenzen, weiche Referenzen, schwache Referenzen und virtuelle Referenzen. Im obigen Abschnitt werden starke und schwache Referenzen vorgestellt. Im Folgenden werden die beiden verbleibenden Soft-Referenzen und virtuellen Referenzen beschrieben.
Weiche Referenz
Eine weiche Referenz ist im Grunde dasselbe wie eine schwache Referenz, mit der Ausnahme, dass sie eine stärkere Fähigkeit hat, zu verhindern, dass der Garbage-Collection-Zeitraum das Objekt, auf das sie verweist, recycelt, als eine schwache Referenz. Wenn ein Objekt über eine schwache Referenz erreichbar ist, wird das Objekt im nächsten Sammelzyklus vom Garbage Collector zerstört. Wenn die Soft-Referenz jedoch erreicht werden kann, bleibt das Objekt länger im Speicher. Der Garbage Collector fordert nur dann Objekte zurück, die über diese Soft-Referenzen erreichbar sind, wenn nicht genügend Speicher vorhanden ist.
Da durch weiche Referenzen erreichbare Objekte länger im Speicher bleiben als durch schwache Referenzen erreichbare Objekte, können wir diese Funktion zum Zwischenspeichern verwenden. Auf diese Weise können Sie viele Dinge speichern. Der Garbage Collector kümmert sich darum, welcher Typ derzeit erreichbar ist und wie hoch der Speicherverbrauch für die Verarbeitung ist.
Phantomreferenz
Im Gegensatz zu weichen und schwachen Referenzen sind die Objekte, auf die virtuelle Referenzen verweisen, sehr fragil, und wir können die Objekte, auf die sie verweisen, nicht über die get-Methode abrufen. Seine einzige Funktion besteht darin, dass es nach der Wiederverwertung des Objekts, auf das es zeigt, zur Referenzwarteschlange hinzugefügt wird, um aufzuzeichnen, dass das Objekt, auf das die Referenz zeigt, zerstört wurde.
Wenn das Objekt, auf das eine schwache Referenz zeigt, für eine schwache Referenz erreichbar wird, wird die schwache Referenz zur Referenzwarteschlange hinzugefügt. Dieser Vorgang findet statt, bevor die Objektzerstörung oder Garbage Collection tatsächlich erfolgt. Theoretisch kann das Objekt, das recycelt werden soll, mit einer nicht konformen Destruktormethode wiederbelebt werden. Aber dieser schwache Bezug wird zerstört. Eine virtuelle Referenz wird der Referenzwarteschlange erst hinzugefügt, nachdem das Objekt, auf das sie verweist, aus dem Speicher entfernt wurde. Seine get-Methode gibt ständig null zurück, um zu verhindern, dass das fast zerstörte Objekt, auf das sie zeigt, wiederbelebt wird.
Es gibt zwei Hauptverwendungsszenarien für virtuelle Referenzen. Dadurch können Sie genau wissen, wann das Objekt, auf das es sich bezieht, aus dem Speicher entfernt wird. Und tatsächlich ist dies die einzige Möglichkeit in Java. Dies gilt insbesondere beim Umgang mit großen Dateien wie Bildern. Wenn Sie feststellen, dass ein Bilddatenobjekt recycelt werden soll, können Sie mithilfe virtueller Referenzen ermitteln, ob das Objekt recycelt wird, bevor Sie mit dem Laden des nächsten Bilds fortfahren. Auf diese Weise können Sie schreckliche Speicherüberlauffehler weitestgehend vermeiden.
Der zweite Punkt ist, dass virtuelle Referenzen viele Probleme bei der Zerstörung vermeiden können. Die finalize-Methode kann Objekte, die kurz vor der Zerstörung stehen, wiederbeleben, indem sie starke Referenzen erstellt, die auf sie verweisen. Allerdings muss ein Objekt, das die Finalize-Methode überschreibt, zwei separate Garbage-Collection-Zyklen durchlaufen, wenn es recycelt werden soll. Im ersten Zyklus wird ein Objekt als wiederverwertbar markiert und kann anschließend vernichtet werden. Aber weil immer noch eine geringe Wahrscheinlichkeit besteht, dass dieses Objekt während des Zerstörungsprozesses wiederbelebt wird. In diesem Fall muss der Garbage Collector erneut ausgeführt werden, bevor das Objekt tatsächlich zerstört wird. Da die Zerstörung möglicherweise nicht sehr zeitnah erfolgt, muss eine unbestimmte Anzahl von Garbage-Collection-Zyklen durchlaufen werden, bevor die Zerstörung des Objekts aufgerufen wird. Dies bedeutet, dass es bei der eigentlichen Reinigung des Objekts zu einer großen Verzögerung kommen kann. Aus diesem Grund erhalten Sie immer noch ärgerliche Fehlermeldungen wegen unzureichendem Arbeitsspeicher, wenn der größte Teil des Heapspeichers als Müll markiert ist.
Durch die Verwendung virtueller Referenzen wird die obige Situation gelöst. Wenn eine virtuelle Referenz zur Referenzwarteschlange hinzugefügt wird, haben Sie absolut keine Möglichkeit, ein zerstörtes Objekt zu erhalten. Denn zu diesem Zeitpunkt wurde das Objekt aus dem Speicher gelöscht. Da eine virtuelle Referenz nicht zum erneuten Generieren des Objekts verwendet werden kann, auf das sie verweist, wird ihr Objekt im ersten Zyklus der Speicherbereinigung bereinigt.
Offensichtlich wird nicht empfohlen, die Finalize-Methode zu überschreiben. Da virtuelle Referenzen offensichtlich sicher und effizient sind, kann das Entfernen der Finalize-Methode die virtuelle Maschine erheblich vereinfachen. Natürlich können Sie diese Methode auch überschreiben, um mehr zu erreichen. Es hängt alles von der persönlichen Entscheidung ab.
Zusammenfassen
Ich denke, dass sich viele Leute darüber beschweren, dass es sich um eine alte API aus den letzten zehn Jahren handelt. Meiner Erfahrung nach kennen sich viele Java-Programmierer nicht so gut aus - Ein tiefes Verständnis ist erforderlich, und ich hoffe, dass jeder aus diesem Artikel etwas gewinnen kann.