© DrHitch/Shutterstock.com
Memory Leaks in Java

3 Umgang mit ungewollten Referenzen: ausnullen oder nicht?


In den vorangegangenen beiden Kapiteln [1], [2] haben wir uns Memory Leaks angesehen, die während des Programmablaufs stetig wachsen und so zum Abbruch des Programms mit OutOfMemoryError führen. Diesmal wollen wir nichtwachsende 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.

Rufen wir uns noch einmal in Erinnerung, wie es zu einem Memory Leak in Java kommt. Der Garbage Collector ermittelt ausgehend von so genannten 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 Stack-Variablen und Feldern

Beginnen wir mit dem Beispiel einer Stack-Variablen von einem Referenztyp, die in der main-Methode definiert wird. Nehmen wir einmal an, diese Stack-Variable stellt eine ungewollte Referenz dar, weil sie ab einem bestimmten Zeitpunkt im Programmablauf auf ein Objekt zeigt, das nicht mehr genutzt wird. Die Stack-Variable 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(argMsg); //2

// der Rest des Programms, das noch lange laeuft
}

Nach dem println() in Zeile //2 wird der über argMsg referenzierte String nicht mehr genutzt. argMsg 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 argMsg ausnullen, nachdem wir sie in println() genutzt haben?

 public static void main(String argv[]) {
String argMsg = "first argument: " + argv[0];
System.out.println(argMsg); //2
argMsg = null; //3

// der Rest des Programms, das noch lange laeuft
}

Ab der Zeile //3 verweist argMsg 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 HotSpot-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 argMsg ist deshalb: Die Referenz sollte 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 argMsg ganz verzichtet und stattdessen folgendes s...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang