Angelika Langer - Training & Consulting
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | CONTACT | Twitter | Lanyrd | Linkedin
 
HOME 

  OVERVIEW

  BY TOPIC
    JAVA
    C++

  BY COLUMN
    EFFECTIVE JAVA
    EFFECTIVE STDLIB

  BY MAGAZINE
    JAVA MAGAZIN
    JAVA SPEKTRUM
    JAVA WORLD
    JAVA SOLUTIONS
    JAVA PRO
    C++ REPORT
    CUJ
    OTHER
 

GENERICS 
LAMBDAS 
IOSTREAMS 
ABOUT 
CONTACT 
Effective Java - Memory Leaks - Referenzen ausnullen

Effective Java - Memory Leaks - Referenzen ausnullen  
Memory Leaks
Referenzen "ausnullen"
 

Java Magazin, Dezember 2012
Klaus Kreft & Angelika Langer

Dies ist die Überarbeitung eines Manuskripts für einen Artikel, der im Rahmen einer Kolumne mit dem Titel "Effective Java" im Java Magazin erschienen ist.  Die übrigen Artikel dieser Serie sind ebenfalls verfügbar ( click here ).

 

In den vorangegangenen beiden Beiträgen (/ ML1 /, / ML2 /) haben wir uns Memory Leaks angesehen, die während des Programmablauf stetig wachsen und so zum Abbruch des Programms mit  OutOfMemoryError führen.  Diesmal wollen wir nicht wachsende Leaks betrachten.  Auf den ersten Blick mögen sie weniger interessant aussehen, weil sie nicht zu solch dramatischen Konsequenzen wie einem Programmabbruch führen.  Interessant sind sie aber trotzdem, weil sie sehr eng mit der vieldiskutierten Frage: "Ausnullen oder nicht?" verbunden sind.

Referenzen "ausnullen"

Rufen wir uns noch mal in Erinnerung, wie es zu einem Memory Leak in Java kommt.  Der Garbage Collector ermittelt ausgehend von sogenannten Root References , welche Objekte in einem Java Programm referenziert und damit erreichbar sind.  Alle nicht erreichbaren Objekte räumt der Garbage Collector bei der Garbage Collection weg und gibt ihren Speicher frei.  Wenn wir nun auf ein Objekt verweisen, von dem wir sicher sagen können, dass wir es im weiteren Kontext unseres Programms gar nicht mehr benutzen werden, haben wir ein Memory Leak.  Denn das nicht mehr benötigte Objekt wird vom Garbage Collector nicht weggeräumt, weil es noch referenziert wird.  Diese Referenz wird in der englischsprachigen Fachliteratur unwanted reference (also: ungewollte Referenz) genannt.
Bei unserem Garbage-Collection-Workshop auf der JAX 2012 tauchte im Zusammenhang mit ungewollten Referenzen die Frage auf, ob man eigentlich grundsätzlich alle Referenzen wenn irgend möglich „ausnullen“ solle.  Der Kollege mache das grundsätzlich so.  Ob es sinnvoll sei.  (Mit „ausnullen“ ist dabei gemeint, dass einer Referenz der Wert  null zugewiesen wird; danach ist das vormals referenzierte Objekt unerreichbar.)  Ganz offensichtlich ist die Frage, wo und unter welchen Umständen man Variablen und Felder in Java ausnullen sollte, ein heiß diskutiertes Thema in agilen Projekten mit gemeinsamer Code-Ownership.  Gehen wir der Frage also nach.

 

Ausnullen bei Stackvariablen und Feldern

Beginnen wir mit dem Beispiel einer Stackvariablen von einem Referenztyp, die in der  main -Methode definiert wird.  Nehmen wir einmal an, diese Stackvariable stellt eine ungewollte Referenz dar, weil sie ab einem bestimmten Zeitpunkt im Programmablauf auf ein Objekt zeigt, das nicht mehr genutzt wird.  Die Stackvariable bewirkt, dass das referenzierte Objekt bis zur Beendigung des  main -Threads (in unserem Fall: bis zum Ende des Programms) erreichbar bleibt.

 

Der konkrete Beispielcode sieht so aus:
 

    public static void main(String argv [] {

      String argMsg = "first argument: " + argv[0];

       System.out.println(arg Msg);       // Zeile 2

      // der Rest des Programms, das  noch lange laeuft

   }

Nach dem  println() in Zeile 2 wird der über  arg Msg referenzierte String nicht mehr genutzt.   arg Msg ist also die ungewollte Referenz, die dafür sorgt, dass der referenzierte String bis zum Programmende lebt.
 

Wie sieht nun die Situation aus, wenn wir die Variable  arg Msg ausnullen, nachdem wir sie in  println() genutzt haben:
 

    public static void main(String argv [] {

      String argMsg = "first argument: " + argv[0];

       System.out.println(arg Msg);       // Zeile 2

       argMsg = null;                     // Zeile 3

      // der Rest des Programms, das  noch lange laeuft

   }

Ab der Zeile 3 verweist  arg Msg auf  null und nicht mehr auf den String:  " first argument:  " + argv[0] . Es existiert auch sonst keine Referenz auf diesen String und der Garbage Collector kann ihn wegräumen und seinen Speicher freigeben.  Es gibt also keine ungewollte Referenz mehr und damit auch kein Memory Leak.  Das Ausnullen hat hier also einen positiven Effekt.
 

Ob das Ausnullen aber wirklich nötig ist, ist eine andere Frage.  Zum einen handelt sich bei dem String um ein relativ kleines Objekt, das nur wenig Speicher verbraucht. Zum anderen gibt es jede Menge Strings ähnlicher Größe, die aus den verschiedensten Gründen sehr lange - unter Umständen bis zum Ende des Programms - leben, obwohl sie gar nicht mehr gebraucht werden.  Nehmen wir als Beispiel nur die beiden Teil-Strings, aus denen unser Beispiel-String gebildet wurde. Der erste Teil  " first argument:  " wird, da es sich um einen String-Literal handelt, im Constant Pool abgelegt.  Dieser liegt bei der Hot Spot JVM in der Permanent Generation . Hier findet im Vergleich zum normalen User Heap ( Young und Old Generation ) die Garbage Collection eher sporadisch statt.  Deshalb lebt dieser String vermutlich lange, möglicherweise bis zum Ende des Programms. Der zweite Teil-String bleibt bis zum Ende des  main -Threads (in unserem Fall ist dies das Ende des Programms) über  argv[0 ] am Leben.
 

Man sieht also, in einem Java Programm gibt es ohnehin häufig kleinere Objekte, die noch weiter am Leben gehalten werden, obwohl sie nicht mehr gebraucht werden.  Die generelle Regel bezüglich des Ausnullens bei einer Variable wie  arg Msg ist deshalb: Die Referenz sollt man dann ausnullen, wenn das referenzierte Objekt sehr groß ist und durch das Ausnullen signifikant viel Speicher freigegeben werden kann.  Dies trifft aber auf den String in unserer Situation oben nicht zu.
 

Das ganze Problem kann man natürlich umgehen, indem man auf die Variable  arg Msg ganz verzichtet und statt dessen folgendes schreibt:
 

    public static void main(String argv [] {

      System.out.println( " first argument: " + argv[0]);

       // der Rest des Programms, das  noch lange laeuft

   }

Der Compiler sieht hier sofort, wie lange er den zusammengesetzten String braucht, und kann ihn unmittelbar nach der Benutzung bereits freigeben, ohne dass wir explizit etwas tun müssen. So wird man es deshalb sinnvollerweise in der Praxis machen.
 

Schauen wir uns nun eine leichte Variante des Beispiels von oben an:
 

    void foo(String  arg ) {

      String argMsg = "first argument: " + arg;

       System.out.println(arg Msg);       // Zeile 2

      argMsg = null;                    // Zeile 3

      //  Rest der Methode, die kurz ist 

   }
 
 

Die Idee dabei ist:  foo() wird aufgerufen, läuft relativ kurz und kehrt danach wieder zur aufrufenden Methode zurück. 
 

Ist in einem solchen Fall das Ausnullen in Zeile 3 sinnvoll?  Nein, denn nachdem der Kontrollfluss aus  foo() zur aufrufenden Methode zurückgekehrt ist, sind die lokalen Variablen ( argMsg ) und Parameter ( arg ) nicht mehr erreichbar. Die von ihnen referenzierten Objekte können, wenn nicht von anderen Stellen noch auf sie verwiesen wird, also freigegeben werden.  Mit dem Ausnullen in Zeile 3 wird der referenzierte String zwar etwas früher unerreichbar; da die Freigabe des Objekts aber ohnehin erst mit der nächsten Garbage Collection erfolgt, ist dieser zeitliche Unterschied gar nicht relevant.  Hier bläht das zusätzliche Ausnullen nur den Code auf und stört.
 

Was gilt eigentlich für block-lokale Variablen?  Hier ein Beispiel:
 

   void foo(String arg) {

     { String argMsg = "first argument: " + arg;

       System.out.println(arg Msg);       // Zeile 2

     }

      //  Rest der Methode, die kurz ist 

   }

Die Variable  argMsg  ist nun block-lokal und nach Verlassen des Blocks im Rest der Methode  foo nicht mehr zugreifbar.  Das legt nahe, das referenzierte String-Objekt sei nach Verlassen des Blocks auch für den Garbage Collector unerreichbar.  In der Praxis ist es aber in der HotSpot-JVM so, dass block-lokale Referenzen erst beim Verlassen der umgebenden Methode aufgegeben werden.  Der von  argMsg  referenzierte lebt daher in der HotSpot-JVM bis zum Verlassen der Methode  foo .  Man könnte die Variable  argMsg  vor Verlassen des Blocks explizit ausnullen, damit der referenzierte String tatsächlich am Blockende unerreichbar wird.  Aber, wie bereits im vorigen Beispiel erläutert, würde es den Code unnötig aufblähen, da der Rest der Methode nur noch kurz ist. 
 

Fassen wir das bisher Gesagte noch mal kurz zusammen.  Ausnullen ist bei methoden- oder block-lokalen Variablen nicht sinnvoll, da die Methoden nach relativ kurzer Zeit wieder verlassen werden und ihre lokalen Variablen damit nicht mehr erreichbar sind.  Ausnahmen sind Methoden (wie  main() ,   run() , ...), die den Ausgangspunkt für länger laufende Threads bilden.  Hier kann Ausnullen in Ausnahmefällen Sinn machen, wenn besonders große Objekte referenziert werden, die im weiteren Programmablauf nicht mehr gebraucht werden. 
 

Eine ähnliche Argumentation gilt im Prinzip auch für Felder von Klassen. Sind die Instanzen langlebig und werden über ihre Felder sehr große Objekte referenziert, sollte man ausnullen.  Sonst eher nicht, weil es nicht viel bringt und eher stört.

Ausnullen bei der Implementierung von eigenen Datenstrukturen

Soweit die grundsätzlichen Überlegungen.  Es gibt weitere spezielle Fälle, bei denen Ausnullen sinnvoll ist, nämlich dann wenn wir eigene Datenstrukturen (Collections, Verwaltungen, ...) implementieren. Das liegt daran, dass wir mit der Implementierung von Datenstrukturen in der Regel größere Mengen Speicher belegen. Deshalb ist es in solchen Situationen wichtig, dass wir kritisch prüfen, ob wir durch Ausnullen den Speicherverbrauch reduzieren können.  Schauen wir uns dazu das Beispiel einer generischen Stack-Collection an:

 

    public class Stack<E> {

       private E[] elements = (E[]) new Object[8];

        private int head = elements.length;

       private void doubleCapacity() { … }

        public void push(E e) {

         elements[--head] = e;

         if (head == 0) doubleCapacity();

        }

        public E pop() {

         if (head == elements.length) return null;

          return (elements[head++]);

        }

    }
 
 

Nehmen wir an, dass wir eine Instanz dieses  Stack s dann folgendermaßen benutzen:
 

    Stack<String> s = new Stack<>();

   s.push("1");        

   s.push("2");                   // Zeile 3

    System.out.println(s.pop);

   System.out.println(s.pop) ;     // Zeile 5
 
 

Im ersten Schritt (bis einschließlich Zeile 3) "pushen" wir zwei Strings auf den Stack.  Danach holen wir diese Strings wieder vom Stack und drucken sie aus.  Wie sieht unsere Stack-Instanz  s am Ende von Zeile 3 aus?  Und wie, wenn alle Statements einschließlich Zeile 5 abgearbeitet worden sind?  Abbildung 1 zeigt eine graphische Repräsentation des Stack-Objekts am Ende von Zeile 3.  Der aktuelle Stackpointer ( head ) zeigt auf das Element mit Index 6 des Arrays ( elements ).  Das bedeutet in unserer Implementierung, dass der Stack zwei Objekte enthält ist.  Auf diese wird von den Array-Elementen unterhalb des Stackpointers verwiesen, also die Elemente mit einem Index  i für den gilt  i >= head .  Das sind in der aktuellen Situation die Indizes 6 und 7.

Abbildung 1: Stack Instanz  s nach Zeile 3
 
 

Kommen wir nun zu Abbildung 2.  Sie zeigt die graphische Repräsentation des Stacks am Ende von Zeile 5.  Der Stackpointer ( head ) steht nun auf Index 8 und zeigt damit auf kein gültiges Element des Array ( elements ), sondern hinter das Array.  Das bedeutet in unserer Implementierung, dass der Stack leer ist.  Das ist okay, denn nach zweimal  push() und zweimal  pop() sollte der Stack auch wieder leer sein.  Auffällig ist, dass die Array-Elemente für Index 6 und 7 immer noch auf die Strings verweisen, die vorher auf den Stack "gepusht" worden sind.  Aus Sicht der Programmlogik ist das kein Problem.  Nur Array-Elemente unterhalb des Stackpointers, also mit einem Index  i , für den gilt  i >= head , sind relevant.  Da aber  head == 8 ist, gibt es gar keine relevanten Elemente.

Abbildung 2: Stack Instanz  s nach Zeile 5
 
 

Von der Logik her ist die Implementierung des  Stack also in Ordnung.  Wie sieht es aber mit dem Speichermanagement?  Sind die Verweise der Array-Elemente auf ihren Inhalt nicht ungewollte Referenzen?  Sie halten doch Objekte referenziert, die sonst unter Umständen freigegeben werden könnten.  Sollten wir die Array-Elemente in  pop() also ausnullen?  Anderseits werden diese Referenzen doch sowieso überschrieben, wenn der  Stack wieder wächst.
 

In diesem Fall ist Ausnullen richtig und wichtig.  Dafür gibt es zwei Gründe.  Den ersten Grund haben wir schon oben erwähnt.  Die nicht leeren Array-Elemente oberhalb des Stackpointers sind ungewollte Referenzen, die dazu führen können, dass die von ihnen referenzierten Objekte nicht freigegeben werden können.  Bei entsprechender Benutzung des  Stack s kann die Summe des so weiterhin ungewollt referenzierten Speichers recht groß werden,  zum Beispiel, weil die Elemente, auf die verwiesen wird, jeweils selbst schon recht groß sind.
 

Der zweite Grund ist subtiler Art.  Collection-Abstrationen wie auch andere Verwaltungsstrukturen sollten möglichst so implementiert sein, dass speicherneutrale Use Cases wirklich speicherneutral sind.  Wir haben in unserem Fall einen speicherneutralen Use Case: zweimal  push() und zweimal  pop() .  Danach sollte genauso viel Speicher von unserem  Stack Objekt gebraucht werden wie vor dem Ausführen des Use Cases.  Das ist aber bei unserer Implementierung nicht der Fall, da die beiden "gepushten" Objekte weiter über den Stack referenziert werden.
 

Im nächsten Artikel dieser Serie wollen wir eine Technik für die Suche von Memory Leaks besprechen, die auf solchen speicherneutralen Uses Cases basiert.  Wenn man diese Technik anwenden will, um ein Memory Leak im eigenen Sourcecode zu suchen, dann ist es hinderlich, wenn man dabei auf Unschönheiten in verwendeten Third-Party-Komponenten wie unseren  Stack stößt.  Es ist äußerst frustrierend, wenn man nach langem Suchen feststellt, dass es sich bei dem vermeintlichen Leak nur um ein false positive in einer Third -Party-Komponenten handelt. 
 

Deshalb ist in einem Fall wie dem oben geschilderten das Ausnullen wichtig, um es den Nutzern der von uns implementierten Abstraktion bei der Memory-Leak-Suche nicht unnötig schwer zu machen. Die korrigierte Version der  pop() Methode sieht nun so aus:
 

        public E pop() {

         if (head == elements.length) return null;

         E result = elements[head];

          eleme n ts[head++] = null;

          return result;

        }
 
 

Natürlich kann es Situationen geben, wo man gewollt gegen die Speicherneutralität verstößt.  Auch bei unserem  Stack passiert es.  Wenn er voll geworden ist, wird mit  doubleCapacity() ein doppelt so großes Array an  elements zugewiesen und die Referenzen auf die Elemente werden umkopiert.  Wenn der  Stack danach wieder leerer wird, bleibt die hohe Kapazität aber erhalten.  Auch wenn man zusätzlich eine Strategie für das Verringern der Kapazität vorsieht, wird diese sinnvollerweise nicht speicherneutral sein. Der  Stack würde sonst bei Größenschwankungen um den Kapazitätsumstellungspunkt herum unperformant arbeiten, da das unterliegende Array immer wieder ersetzt werden müsste. Die Regel lautet deshalb ein wenig einschränkend: Soweit es ohne weitere Nachteile möglich ist, sollte die Implementierung von Datenstrukturen speicherneutral sein.

Zusammenfassung und Ausblick

Dieser Artikel ist von einer Diskussion bei unserem Garbage-Collection-Workshop auf der JAX 2012 inspiriert.  Es ging um die Frage, wo und unter welchen Umständen man Variablen und Felder in Java ausnullen sollte.  Dieser Artikel fasst unsere Sicht zusammen. 
Im Allgemeinen gilt: Variablen und Felder sollte man nur dann explizit ausnullen, wenn man signifikant viel Speicher damit freigeben kann.  Etwas anders sieht es bei Library-Abstrationen und Frameworks aus.  Hier sollte man etwas strenger sein.  Zum einen hat man es unter Umständen nicht selbst unter Kontrolle, wie groß der unnötig referenzierte Speicher ist, wie man bei unserem generischen Stack-Beispiel oben sehen kann.  Zum anderen sollten Library-Abstraktionen möglichst speicherneutral sein, damit ihre Benutzer bei ihrer eigenen Suche nach Memory Leaks nicht durch false positives aus den Library-Abstraktionen behindert werden.

 

Wie schon kurz erwähnt, werden wir uns in unserem nächsten Artikel Strategien und Tools ansehen, mit denen man Memory Leaks suchen kann.
 
 
Kuriositäten-Kabinett
Noch eine Randbemerkung zum Ausnullen von Referenzen bei der Implementierung von verketteten Datenstrukturen: Bei knotenbasierten, verketteten Datenstrukturen verweist jeder Knoten auf andere benachbarte Knoten in der Datenstruktur.  In einer Methode, die Elemente aus der Datenstruktur entfernt, werden Knoten abgehängt. Für das Abhängen ist das Ausnullen in der Regel zwingend erforderlich.  Korrektes Abhängen von Knoten genügt aber nicht immer – wie das folgende Beispiel illustriert.
Nehmen wir an, es geht um eine doppelt-verkettete FIFO-Queue.  Jeder Knoten hat Verweise zum Vorgänger ( prev ) und Nachfolger ( next ) und natürlich auf das eigentliche Element ( item ).  Die Queue selbst hält Verweise auf den ersten und letzten Knoten ( first und  last ).  Die  put() -Methode fügt neue Elemente hinten ein, die  take() -Methode nimmt Elemente vorne heraus.

 
Abb.1: Queue vor dem  take()
Abb.2: Queue nach dem  take()
public final class Queue<E> {
    static class Node<E> {
        E item; 
         Node<E> next, prev;
        …

    }

    private Node<E> first, last;

    void put(E e) { … }

    public E take() {

        Node<E> f = first;

        if (f == null)

            throw new NoSuchElementException();

        E element = f.item;

        Node<E> next = f.next;

        first = next;

        if (next == null)

            last = null;

        else

            next.prev = null;    // 1

        return element;

     }

}

 

Die  take() -Methode hängt in Zeile // 1 den zu entfernenden (ehemaligen ersten) Knoten ab.  Er ist danach unerreichbar und kann vom Garbage Collector weggeräumt werden.  Dieser abgehängte Knoten enthält Referenzen, die nicht aufgelöst wurden: die Referenz  next auf den Nachfolgeknoten und die Referenz  item auf das Element.  Muss man diese Referenzen auch noch ausnullen? 

Streng genommen müsste man es nicht tun, denn alles was an dem bereits unerreichbaren Knoten hängt, ist ebenfalls unerreichbar und könnte vom Garbage Collector frei gegeben werden.  Man spricht bei solchen Zombie-Objekten, die zwar unerreichbar, aber noch nicht weggeräumt sind, von floating garbage .  Wie lange floating garbage im Speicher stehen bleibt, hängt von den Umständen ab.  Wenn es ungünstig auskommt, dann kann es ziemlich lange dauern, bis sie freigegeben sind - insbesondere dann, wenn der Eingangsknoten in ein Geflecht von Zombie-Objekten in der Old Generation oder gar in der Permanent Generation liegt, denn dort findet Garbage Collection im Vergleich zur Young Generation selten statt. 

In unserem Beispiel könnte es passieren, dass ein abgehängter Knoten in der Old Generation liegt (siehe Abbildung 3) und über die Verweise auf die ehemaligen Nachfolgeknoten eine ganze Kette von bereits unerreichbaren Knoten in der Young Generation (und den daran hängenden Elementen) im Speicher hält.  In einer solchen Situation reicht nämlich Garbage Collection auf der Young Generation nicht aus um die abgehängten Knoten in der Young Generation freizugeben.  Erst nach einer Garbage Collection auf der Old Generation werden alle abgehängten Knoten auch freigegeben. Ob eine solche Situation stört, hängt von der Menge an floating garbage ab, die dabei entsteht und der Häufigkeit der Garbage-Collections auf der Old-Generation.  
 
Abb.3: Floating Garbage nach mehreren  take() -Aufrufen
 
Korrekt müsste die die  take() -Methode so aussehen:
    public E take() {
        Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();

        E element = f.item;

        f.item = null;

        Node<E> next = f.next;

        f.next = null; 

        first = next;

        if (next == null)

            last = null;

        else

            next.prev = null;

        return element;

     }

Das beschriebene Problem ist in der Praxis tatsächlich vorgekommen: es hat kurzzeitig eine Version der  java.util.LinkedBlockingQueue im JDK gegeben, die das beschriebene Defizit hatte, und das Problem ist tatsächlich einigen Benutzern als temporäres Memory Leak aufgefallen. 

Fazit: Man sollte bei der Implementierung von  remove() -artigen Methoden in Datenstrukturen genau überlegen, was alles ausgenullt werden sollte.  Selbst floating garbage, der garantiert irgendwann von alleine verschwindet, kann stören.

Literaturverweise

Die gesamte Serie über Memory Leaks:

/MEMLKS-1/ Memory Leaks - Ein Beispiel
Klaus Kreft & Angelika Langer, Java Magazin, August 2012
URL: http://www.angelikalanger.com/Articles/EffectiveJava/64.Mem.Leaks/64.Mem.Leaks.html
/MEMLKS-2/ Akkumulation von Memory Leaks
Klaus Kreft & Angelika Langer, Java Magazin, Oktober 2012
URL: http://www.angelikalanger.com/Articles/EffectiveJava/65.Mem.Akkumulation/65.Mem.Akkumulation.html
/MEMLKS-3/ Memory Leaks - Referenzen "ausnullen"
Klaus Kreft & Angelika Langer, Java Magazin, Dezember 2012
URL: http://www.angelikalanger.com/Articles/EffectiveJava/66.Mem.NullOut/66.Mem.NullOut.html
/MEMLKS-4/ Tools für die dynamisch Memory Leak Analyse
Klaus Kreft & Angelika Langer, Java Magazin, Februar 2013
URL: http://www.angelikalanger.com/Articles/EffectiveJava/67.MemLeak.ToolCyclic/67.MemLeak.ToolCyclic.html
/MEMLKS-5/ Heap Dump Analyse
Klaus Kreft & Angelika Langer, Java Magazin, April 2013
URL: http://www.angelikalanger.com/Articles/EffectiveJava/68.MemLeak.ToolDump/68.MemLeak.ToolDump.html
/MEMLKS-6/ Weak References
Klaus Kreft & Angelika Langer, Java Magazin, Juni 2013
URL: http://www.angelikalanger.com/Articles/EffectiveJava/69.MemLeak.WeakRefs/69.MemLeak.WeakRefs.html

 
 
 

If you are interested to hear more about this and related topics you might want to check out the following seminar:
Seminar
 
Effective Java - Advanced Java Programming Idioms 
4 day seminar ( open enrollment and on-site)
High-Performance Java - Profiling and Tuning Java Applications
4 day seminar ( open enrollment and on-site)
 

 
  © Copyright 1995-2016 by Angelika Langer.  All Rights Reserved.    URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/66.Mem.NullOut/66.Mem.NullOut.html  last update: 29 Nov 2016