Inhaltsverzeichnis Inhaltsverzeichnis Einführung...2 Der Java Garbage Collector...2 Java Memory Leaks...4 Werkzeuge zur Speicheranalyse...



Ähnliche Dokumente
2A Basistechniken: Weitere Aufgaben

Datensicherung. Beschreibung der Datensicherung

Grundlagen von Python

Objektorientierte Programmierung für Anfänger am Beispiel PHP

! " # $ " % & Nicki Wruck worldwidewruck

Zählen von Objekten einer bestimmten Klasse

L10N-Manager 3. Netzwerktreffen der Hochschulübersetzer/i nnen Mannheim 10. Mai 2016

Software-Engineering und Optimierungsanwendungen in der Thermodynamik

Er musste so eingerichtet werden, dass das D-Laufwerk auf das E-Laufwerk gespiegelt

Handbuch Fischertechnik-Einzelteiltabelle V3.7.3

Das sogenannte Beamen ist auch in EEP möglich ohne das Zusatzprogramm Beamer. Zwar etwas umständlicher aber es funktioniert

Kap. 35 Swing: Grundlagen Kap Swing: Hauptfenster

Erstellen von x-y-diagrammen in OpenOffice.calc

Outlook. sysplus.ch outlook - mail-grundlagen Seite 1/8. Mail-Grundlagen. Posteingang

Berechtigungen im Kalender Anleitung für die Rechtevergabe im Outlook Kalender FHNW, Services, ICT

Handbuch. Artologik EZ-Equip. Plug-in für EZbooking version 3.2. Artisan Global Software

Vorkurs C++ Programmierung

Einrichten einer Festplatte mit FDISK unter Windows 95/98/98SE/Me

Professionelle Seminare im Bereich MS-Office

.NET Code schützen. Projekt.NET. Version 1.0

1 topologisches Sortieren

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren

Algorithmen und Datenstrukturen

Anleitung zum erfassen von Last Minute Angeboten und Stellenangebote

GUI Programmierung mit JAVA Swing

Anmeldung bei einem registrierten Konto (Account)

Diese Ansicht erhalten Sie nach der erfolgreichen Anmeldung bei Wordpress.

Fachbericht zum Thema: Anforderungen an ein Datenbanksystem

Installation des Authorware Webplayers für den Internet Explorer unter Windows Vista

SafeRun-Modus: Die Sichere Umgebung für die Ausführung von Programmen

Einführung in die Programmierung

Step by Step Webserver unter Windows Server von Christian Bartl

Übungen zu Einführung in die Informatik: Programmierung und Software-Entwicklung: Lösungsvorschlag

Partitionieren in Vista und Windows 7/8

Nicht kopieren. Der neue Report von: Stefan Ploberger. 1. Ausgabe 2003

Dieser Ablauf soll eine Hilfe für die tägliche Arbeit mit der SMS Bestätigung im Millennium darstellen.

2. ERSTELLEN VON APPS MIT DEM ADT PLUGIN VON ECLIPSE

Stellen Sie bitte den Cursor in die Spalte B2 und rufen die Funktion Sverweis auf. Es öffnet sich folgendes Dialogfenster

Einrichtung des Cisco VPN Clients (IPSEC) in Windows7

5 DATEN Variablen. Variablen können beliebige Werte zugewiesen und im Gegensatz zu

Drei-Schichten-Architektur. Informatik B - Objektorientierte Programmierung in Java. Vorlesung 16: 3-Schichten-Architektur 1 Fachkonzept - GUI

Es sollte die MS-DOS Eingabeaufforderung starten. Geben Sie nun den Befehl javac ein.

iphone-kontakte zu Exchange übertragen

Verschlüsseln Sie Ihre Dateien lückenlos Verwenden Sie TrueCrypt, um Ihre Daten zu schützen.

25 Import der Beispiele

Handbuch B4000+ Preset Manager

Arbeiten mit UMLed und Delphi

Internet Explorer Version 6

Installation und Inbetriebnahme von Microsoft Visual C Express

Windows 7 Winbuilder USB Stick

Software Engineering Klassendiagramme Assoziationen

TTS - TinyTimeSystem. Unterrichtsprojekt BIBI

Übung: Verwendung von Java-Threads

Inhaltsverzeichnis. 1. Empfängerübersicht / Empfänger hinzufügen 2. Erstellen eines neuen Newsletters / Mailings 3. Versand eines Newsletters

Bedienungsanleitung. Stand: Copyright 2011 by GEVITAS GmbH

Tutorial about how to use USBView.exe and Connection Optimization for VNWA.

Sich einen eigenen Blog anzulegen, ist gar nicht so schwer. Es gibt verschiedene Anbieter. ist einer davon.

How to do? Projekte - Zeiterfassung

Was meinen die Leute eigentlich mit: Grexit?

Im Folgenden wird Ihnen an einem Beispiel erklärt, wie Sie Excel-Anlagen und Excel-Vorlagen erstellen können.

Datenbank-Verschlüsselung mit DbDefence und Webanwendungen.

Aufklappelemente anlegen

5.2 Neue Projekte erstellen

Qt-Projekte mit Visual Studio 2005

.htaccess HOWTO. zum Schutz von Dateien und Verzeichnissen mittels Passwortabfrage

iloq Privus Bedienungsanleitung Schließanlagen Programmierung Version 1 - Copyright 2013

Programme im Griff Was bringt Ihnen dieses Kapitel?

Computeria Solothurn

Prof. Dr. Uwe Schmidt. 21. August Aufgaben zur Klausur Objektorientierte Programmierung im SS 2007 (IA 252)

Programmierkurs Java

mysql - Clients MySQL - Abfragen eine serverbasierenden Datenbank

Stundenerfassung Version 1.8 Anleitung Arbeiten mit Replikaten

Wo möchten Sie die MIZ-Dokumente (aufbereitete Medikamentenlisten) einsehen?

Installation älterer Programmversionen unter Windows 7

Das Leitbild vom Verein WIR

Folgeanleitung für Klassenlehrer

Anzeige von eingescannten Rechnungen

SANDBOXIE konfigurieren

Beispiel Shop-Eintrag Ladenlokal & Online-Shop im Verzeichnis 1

AutoCAD Dienstprogramm zur Lizenzübertragung

FTP-Server einrichten mit automatischem Datenupload für

icloud nicht neu, aber doch irgendwie anders

WordPress. Dokumentation

Schritt-Schritt-Anleitung zum mobilen PC mit Paragon Drive Copy 10 und VMware Player

Einrichten einer mehrsprachigen Webseite mit Joomla (3.3.6)

Novell Client. Anleitung. zur Verfügung gestellt durch: ZID Dezentrale Systeme. Februar ZID Dezentrale Systeme

Klassenentwurf. Wie schreiben wir Klassen, die leicht zu verstehen, wartbar und wiederverwendbar sind? Objektorientierte Programmierung mit Java

Objektorientierte Programmierung

GSM Scanner Bedienungsanleitung

AGROPLUS Buchhaltung. Daten-Server und Sicherheitskopie. Version vom b

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

Erklärung zum Internet-Bestellschein

Abteilung Informatik, JFC/Swing 2004 Diego Schmidlin V2.2

Einstellungen im Internet-Explorer (IE) (Stand 11/2013) für die Arbeit mit IOS2000 und DIALOG

Hex Datei mit Atmel Studio 6 erstellen

Online Newsletter III

SEP 114. Design by Contract

Leitfaden zur ersten Nutzung der R FOM Portable-Version für Windows (Version 1.0)

4. Jeder Knoten hat höchstens zwei Kinder, ein linkes und ein rechtes.

Der Kalender im ipad

Transkript:

Java Memory Leaks Seminararbeit von Bosco Germano 6Ie Fachhochschule Aargau Departement Technik Studiengang I Betreuender Dozent: Prof. Dr. D. Gruntz Windisch, 6. Juli 2002

Abstract Many people think that Java is immune to memory leaks. In fact these people are wrong. Memory management is a consideration in Java programming as it is in C or C++. Even if Java s Garbage Collector solves the main cause of memory leaks, we can still have leaks in Java programs. Loiterers, as the Java memory leaks are called, are mainly due to unwanted object references. These references prevent the Garbage Collector from recycling the referenced objects. To clarify how these loiterers occur, a few different patterns of loitering objects have been identified.

Inhaltsverzeichnis Inhaltsverzeichnis 1 Einführung...2 2 Der Java Garbage Collector...2 2.1 Wie funktioniert der Garbage Collector...2 2.2 Gerüchte rund um den GC...4 3 Java Memory Leaks...4 Loiterers...4 3.1 Auswirkungen von Loiterers...5 3.2 Lexikon der Loiterers...5 3.2.1 Lapsed Listeners...6 3.2.2 Lingerers...6 3.2.3 Laggards...7 3.2.4 Limbo...8 3.3 Schwache Referenzen...9 4 Werkzeuge zur Speicheranalyse...9 4.1 Aufspüren von Memory Leaks mit Sitraka JProbe...10 Schluss...12 A. Anhang...13 A1. Listings...13 A2. Literaturverzeichnis...16 1

1 Einführung 1 Einführung Java löst das Problem der Memory Leaks. Diese Aussage kann man in ähnlicher Form in beinahe jedem Java-Buch nachlesen. Von Memory Leaks sprechen wir, wenn ein Programm Speicher alloziert, und diesen später nicht mehr dem Betriebssystem zurückgibt. Memory Leaks gelten in Sprachen wie C oder C++ zu den berüchtigtsten Programmierfehlern überhaupt. Java allerdings verspricht durch seinen eingebauten Garbage Collector dieses Problem zu lösen. Einige Java-Entwickler haben allerdings bei ihren Programmen klassisches Memory Leak - Verhalten bemerkt, das heisst die Programme brauchen mehr und mehr Speicher bis es schliesslich sogar zum Absturz kommt. Diese Arbeit beschäftigt sich mit der Frage, ob es in Java trotz Garbage Collector Memory Leaks gibt und wie man diese finden kann. 2 Der Java Garbage Collector 2.1 Wie funktioniert der Garbage Collector Um den Garbage Collector zu verstehen, müssen wir zuerst einen Blick auf Javas Memory Management werfen. Alle Objekte werden in Java mit dem new Operator alloziert und über Referenzen angesprochen. Wir können uns den Java-Heap als gerichteten Graph vorstellen. Objekte bilden dabei die Knoten des Graphen, während die Referenzen als Kanten zwischen den Knoten angesehen werden können. Dies ist auch die Art wie der Garbage Collector den Speicher sieht. Ziel des Garbage Collectors ist es, nicht mehr gebrauchte Objekte aus dem Speicher zu entfernen. Dies ist eine schwierige Aufgabe, da der Garbage Collector nicht wissen kann, ob ein bestimmtes Objekt noch gebraucht wird oder nicht. Der Garbage Collector arbeitet mit der Analogie des gerichteten Graphen; ausgehend von so genannten Wurzelknoten traversiert er den ganzen Graphen. Alle Objekte, welche beim Traversieren des Graphen nicht besucht wurden, werden als garbage behandelt und somit vom Garbage Collector abgeräumt. Dabei gibt es drei verschiedene Arten von Wurzelknoten: statische Felder in Klassen, gerade ausgeführte Java- Methode, globale Variablen in nativen Methoden oder eine Exception in einem Exception Handler. Wir veranschaulichen diesen Vorgang an einem kleinen Beispiel. Listing 1 zeigt ein einfaches Programm, welches ein Customer-Objekt instanziert, und darauf die Methode getbalance() aufruft. 2

2 Der Java Garbage Collector Listing 1. Das Customer-Objekt public static void main(string[] args) Customer fred = new Customer("Fred"); fred.getbalance(); System.out.println("Thank you" + fred); System.out.println("End of program"); // Within the Customer Class public double getbalance() BalanceUtility balanceutility = new BalanceUtility(); double balance = balanceutility.computebalance(this); return balance; public void finalize() System.out.println("A Customer is being garbage collected"); Sobald wir in diesem Beispiel nach dem Ausführen der getbalance() Methode zur main- Methode zurückkehren, gibt es keinerlei Referenzen mehr, die auf balanceutility zeigen. Dieses Objekt hat also keine Verbindung mehr zum Graphen, wird also vom Garbage Collector als potenzieller Abfall angeschaut. Was nicht heisst, dass dieses Objekt sofort vom Garbage Collector abgeräumt werden wird. In der Tat verwenden verschiedene Java VMs auch verschiedene Algorithmen für ihre Garbage Collectoren. Bild 1. Das balanceutility-objekt wird nicht mehr referenziert, also darf es vom GC abgeräumt werden. Eine Möglichkeit um das Abräumen eines Objektes einfach zu kontrollieren, ist die finalize() Methode. Diese Methode wird vom Garbage Collector aufgerufen kurz bevor das betreffende Objekt abgeräumt wird. Leider haben wir keine Garantie, dass diese Methode auch tatsächlich ausgeführt werden wird. In früheren Java VMs (1.1.x) gab es eine Methode runfinalizersonexit(true), welche uns diese Garantie zusicherte. Diese Methode existiert zwar noch in Java Version 2, ist allerdings deprecated, da sie bei Applikationen mit mehreren Threads zu Deadlocks führen kann. 3

2 Der Java Garbage Collector 2.2 Gerüchte rund um den GC Zum Thema GC gibt es einige Gerüchte, welche man immer wieder hört. Die zwei häufigsten seien hier kurz erwähnt und klargestellt. Das erste Gerücht besagt, dass der GC mit zyklischen Graphen nicht klarkommt. Wenn wir zum Beispiel drei Objekte A, B und C haben, mit Referenzen von A nach B, von B nach C und von C nach A, und auf diese drei Objekte keine weiteren Referenzen existieren, dann wird der Garbage Collector die drei Objekte abräumen. Bei anderen Systemen, die mit anderen Algorithmen arbeiten (z. B. Microsofts COM Technologie, welche mit Referenzzähler arbeitet), treten jedoch durchaus Probleme beim Behandeln von zyklischen Referenzgraphen auf. Das zweite Gerücht stammt von Entwicklern, die von C++ auf Java gewechselt haben. Die Behauptung ist, dass der finalizer in Java das gleiche sei wie die Destruktoren in C++. Das ist aber nicht richtig, da es einige Unterschiede zwischen den Finalizern und den Destruktoren gibt. Ein C++-Destruktor wird garantiert ausgeführt, und zwar unmittelbar beim delete Aufruf. Bei den Java-Finalizer garantiert uns niemand, dass diese auch wirklich ausgeführt werden. 3 Java Memory Leaks 3.1 Loiterers In Bild 2 sehen wir einen Java Heap als Graphen dargestellt. Jedes instanzierte Objekt befindet sich in einem von drei möglichen Zuständen: Reachable (Live) sind alle Objekte welche von einem Wurzelknoten aus erreichbar sind, und vom laufenden Programm noch benötigt werden. Reachable (Loitering) sind Objekte welche ebenfalls von einem Wurzelknoten erreichbar sind, die aber vom laufenden Programm nicht mehr benötigt werden. Not Reachable Objekte sind von den Wurzelknoten aus nicht mehr zu erreichen, sie können also beim nächsten Durchgang des GC abgeräumt werden. In Sprachen wie C++ würden die Objekte des Types not reachable die memory leaks bilden. Bei Java sieht es etwas anders aus, da sich ja der GC um die not reachable Objekte kümmert. Java Memory Leaks sind reachable Objekte, die noch referenziert sind, aber logisch nicht mehr gebraucht werden. Wie wir sehen unterscheiden sich Memory Leaks in C++ und Java ziemlich stark, oft ist es wichtig unterscheiden zu können ob wir von einem Java oder C++ Memory Leak sprechen, z. B. beim debuggen einer VM, welche teilweise aus C-Code besteht, aber auch aus Java-Code. Aus diesem Grund hat sich für Java Memory Leaks ein eigener Begriff durchgesetzt, nämlich loiterer. Das englische Wörterbuch liefert als Definition von Loiterer: an einem Ort sein, ohne offensichtlichen Grund. Auf deutsch etwa herumlungern. Dies trifft den Nagel auf dem Kopf Loiterer sind Objekte welche zwar im Heap sind aber eigentlich gar keinen Grund mehr haben, dort zu sein. 4

3 Java Memory Leaks Bild 2. Java Heap mit loitering - Objekten. 3.2 Auswirkungen von Loiterers Wenn sich ein Programm nach einer Weile mit der Meldung java.lang.outofmemoryerror verabschiedet, ist die Chance recht gross, dass wir es mit einem Memory Leak zu tun haben. Abgesehen von diesem ziemlich offensichtlichen Grund, wann müssen wir uns Sorgen machen um Memory Leaks? Ein Perfektionist würde wohl sagen, jedes Memory Leak muss gefunden und behoben werden. Bei Java haben allerdings nicht alle Memory Leaks gravierende Folgen. Die zwei wichtigsten Punkte, die wir berücksichtigen müssen, sind die Lebensdauer des Pogrammes und die Grösse des Leaks. Die Lebensdauer des Programms ist aus folgendem Grunde wichtig. Bei Java wird der Speicher von der VM verwaltet. Beim Beenden des Programms wird meist auch die VM beendet, und gibt auch den gesamten Speicher dem Betriebssystem zurück. Ein Memory Leak in einem Java- Programm, das nur ein sehr beschränkte Lebensdauer hat, wird also kaum Folgen haben. Ganz anders verhält es sich bei Programmen, die rund um die Uhr laufen müssen. Ein gutes Beispiel sind Server-Applikationen. Hier wird auch der kleinste Memory Leak früher oder später dazu führen, dass die VM all ihren Speicher aufbraucht und mit einer Exception abbricht. Die Grösse der Memory Leaks ist ebenfalls ein wichtiger Punkt. Memory Laeks in C sind normalerweise einzelne Objekte, in Java sind die Leaks selten einzelne Objekte, sondern meistens ganze Bäume von Objekte welche sich gegenseitig referenzieren. Ein Java Memory Leak kann also aus sehr vielen Objekten bestehen und sehr schnell ziemlich gross werden. 3.3 Lexikon der Loiterers Wir wissen jetzt, wie wir uns Memory Leaks in Java vorstellen können. Doch wann und wie treten sie auf? Ähnlich den Patterns bei der Programmierung, existieren auch bei den Loiterers verschiedene typische Fälle bei denen diese entstehen können. Die wichtigsten dieser Fälle werden im Folgenden behandelt. 5

3 Java Memory Leaks 3.3.1 Lapsed Listeners Ein Lapsed Listener (zu deutsch etwa: verfallener Listener) ist ein Objekt, der einer Collection zugefügt wurde, nach Gebrauch aber nicht mehr aus der Collection entfernt wurde. Ein typisches Beispiel für diese Art von Loiterer sind Event-Listeners, die einer Listener-Liste zugefügt wurden, und die nicht mehr aus der Liste gelöscht wurden, obwohl sie gar nicht mehr gebraucht werden. Falls einer Listener-Liste immer wieder neue Listener zugefügt werden, die alten jedoch nie gelöscht, kann diese sehr gross werden. Es müssen also immer mehr Listener notifiziert werden. Entsprechend kann das Notifizieren aller Listener immer länger dauern und zu einem echten Performance-Problem werden. Diese Art von Loiterers ist vermutlich die häufigste, da Java- Swing und AWT ziemlich anfällig auf solche Probleme sind. Ein Beispiel für einen Lapsed Listener ist die Methode addpropertychangelistener() aus java.awt.toolkit(). Mit dieser Methode kann ein Listener registriert werden, der immer dann notifiziert wird, wenn eine Eigenschaft des Desktops ändert. Die Toolkit-Klasse ist als Singleton implementiert, sie wird also beim Start der Applikation erstellt und bleibt bis zum Schluss bestehen. Die meisten Listener, werden aber kaum so lange gebraucht werden. Da aber der Toolkit eine Referenz auf sie hat, können sie nicht von Garbage Collector aufgeräumt werden. Dieses Problem tritt dann auf, wenn ein Objekt mit sehr langer Lebensdauer eine Referenz auf Objekte mit kurzer Lebensdauer hat. Das kurzlebige Objekt wird dazu verdonnert im Speicher zu bleiben bis auch das langlebige nicht mehr gebraucht wird. Im Falle des Toolkit gibt es eine einfache Lösung um das Problem zu umgehen. Wir müssen die Methode removepropertychangelistener() aufrufen und zwar jedes Mal wenn wir einen Listener nicht mehr brauchen. Dies entspricht auch der allgemeinen Lösung für dieses Problem: Jedes Mal, wenn wir eine Methode im Stil addxxxlistener() implementieren, müssen wir auch eine Methode removexxxlistener() haben, und diese bei Bedarf auch aufrufen. 3.3.2 Lingerers Die zweite Art von Loiterer sind die so genannten Lingerers (zu deutsch: zögern, sich nicht halten können). Lingerers sind Objekte, die eine Weile rumzögern, obwohl sie nicht mehr gebraucht werden. Als Beispiel für Lingerer schauen wir Listing 2 an. Wir haben es nochmals mit einem Singleton zu tun, diesmal ein PrintService. In der PrintService-Klasse gibt es ein target-feld. Beim Aufruf der doprint()-methode wird das von target referenzierte Objekt ausgedruckt. Falls die doprint()-methode target nicht explizit auf null setzt, haben wir einen Lingerer. Das von target referenzierte Objekt wird nicht mehr gebraucht, bleibt aber trotzdem im Speicher bis der nächste Druckauftrag kommt. Der Memory Leak ist in diesem Falle zwar nur temporär, aber dafür umso grösser. Insbesondere beim PrintService Beispiel, da Druckaufträge unter Umständen sehr gross werden können. 6

3 Java Memory Leaks Listing 2. Ein typischer PrintService public class PrintService static PrintService singleton; Printable target; public PrintService getprintservice() return singleton; public void settarget(printable p) target = p; public void doprint() // set stuff up // print target Um diese Art von Leaks zu verhindern, gibt es eine einfache Lösung. Jede Methode muss nachdem ihre Eigentliche Aufgabe erfüllt ist, auch aufräumen. In diesem Falle müsste die doprint()-methode() das target-feld auf null setzten, damit der Garbage Collector in Aktion treten kann. 3.3.3 Laggards Die dritte Art von Loiterer ist der Laggard (zu deutsch: Bummler). Also jemand der stets etwas zurück bleibt. Laggards treten bei Objekten auf, die ihren Zustand ändern, aber weiterhin Referenzen zu Daten des alten Zustandes besitzen. Hinter Laggards stecken meistens funktionale Fehler, also echte Bugs. Diese sind aber ziemlich schwer zu lokalisieren und zeigen sich oft zuerst nur als Memory Leaks. Auch Laggards treten oft im Zusammenhang mit Singletons auf. Nehmen wir an, wir haben eine Klasse, die mehrere Instanzen haben darf. Nun müssen wir diese Klasse neu als Singleton schreiben, weil es z.b. zu aufwendig ist so viele Instanzen dieser Klasse zu erstellen. Da es jetzt nur eine Instanz der Klasse gibt, ist diese gezwungen, ihren Zustand oft zu ändern. Als Beispiel für einen Laggard können wir uns eine Klasse vorstellen, welche für ein bestimmtes Verzeichnis jeweils eine Referenz zur grössten, der kleinsten und der neusten Datei besitzt. Weisen wir nun der Klasse ein anderes Verzeichnis zu, müssten diese drei Referenzen angepasst werden. Falls stattdessen nur die Referenzen zur grössten und der kleinsten Datei angepasst werden, haben wir ein Laggard. Die Referenz zum neusten File, zeigt nämlich noch auf eine Datei im alten Verzeichnis. Wir haben es hier offensichtlich mit einem Programmierfehler zu tun, allerdings mit einem der recht schwer zu finden ist. 7

3 Java Memory Leaks Laggards können verhindert werden, indem wir bei Klassen, die ihren Zustand ändern müssen, möglichst alle Änderungen zentral in einer Methode durchführen. Auch kann man sich überlegen, welche Werte wir als Zustand einer Klasse speichern wollen und welche nicht. In gewissen Fällen ist es ratsam die Werte dynamisch bei Bedarf auszulesen. Bei unserem vorherigen Beispiel könnte man die neuste Datei z. B. dynamisch ermitteln anstatt sie als Referenz in der Klasse zu speichern. Dies natürlich nur, falls der Wert nicht allzu oft gebraucht würde. 3.3.4 Limbo Die vierte Art von Loiterer ist der Limbo. Sachen im Limbo sind zwischen zwei Welten gefangen. Objekte im Limbo sind nicht von langer Lebensdauer, brauchen dafür umso mehr Speicher. Limbos treten auf, wenn grosse nicht mehr gebrauchte Objekte vom Stack referenziert werden, und auf einer Methode/Thread warten müssen, die/der eine sehr lange Laufzeit hat, warten muss. Um das Ganze etwas zu veranschaulichen schauen wir ein kleines Beispiel an. Die method()- Methode in Listing 3 soll eine sehr grosse Datei einlesen, anschliessend sollen gewisse Elemente aus der grossen Datei von der parseit()-methode verarbeitet werden. Zuerst wird die readit()-methode aufgerufen, diese liest die gesamte Inputdatei ein, und belegt entsprechend viel Speicher. Daraufhin extrahiert findit() die relevanten Informationen aus dem big-objekt und speichert diese im viel kleineren item-objekt. Ab diesem Augenblick brauchen wir das big- Objekt nicht mehr und möchten am liebsten dessen Speicher für andere Zwecke nutzen. Sobald aber parseit() aufgerufen wird, hat der GC keine Chance mehr das big-objekt abzuräumen, da die method-methode im Stack noch eine Referenz darauf hat. Es bleibt uns nichts anderes übrig als abzuwarten bis die Methode zurückkehrt. Erst beim Beenden von method() wird das big- Objekt vom GC gelöscht. Listing 3. Methode um eine grosse Datei zu parsen void method() // this creates a large object Biggie big = readit(); // this condenses it Item item = findit(big); // we'd really like to reuse big's memory // big = null; // this method is going to run a long time parseit(item); 8

3 Java Memory Leaks Die einfachste Lösung für diese Art von Problemen ist das explizite null Setzen der big- Referenz. Wie auch in der auskommentierten Zeile im Listing sichtbar. Natürlich ist es in Java nicht notwendig alle Referenzen nach Gebrauch null zu setzten. Trotzdem ist es bei einigen Objekten wichtig, damit wir nicht grosse Objekte haben, die im Heap behalten werden, aufgrund einer Stackreferenz. 3.4 Schwache Referenzen Wie wir gesehen haben, entstehen die meisten Memory Leaks wegen ungewollten Referenzen. Alle bisherigen Lösungsvorschläge erfordern, dass diese Referenzen vom Programmierer explizit null gesetzt werden. Seit der JDK 1.2 bietet Java auch eine weitere Lösung gegen ungewollte Referenzen an, die so genannten Weak References. Eine Weak Reference ist eine Art Referenz, die den Garbage Collector nicht daran hindert das Objekt abzuräumen. Beim Traversieren des Heap-Baumes folgt der Garbage Collector nur den normalen Referenzen, ein Objekt, das nur noch durch Weak References erreichbar ist, wird also vom Garbage Collector nicht erreicht undwird somit als Garbage angeschaut. Die schwachen Referenzen sind in folgender Klasse implementiert: java.lang.ref.weakreference. Ein Beispiel für die Verwendung von Weak References ist Listing 5: LeakExampleFixed.java welches im Anhang gefunden werden kann. Dieses Beispiel wird im Zusammenhang mit den Speicheranalysetools im nächsten Kapitel genauer behandelt. Eine weitere nützliche Klasse, die mit JDK 1.2 eingeführt wurde, ist die WeakHashMap. Diese Klasse ist eine Kombination von HashMap und WeakReference. Die WeakHashMap besitzt nur WeakReferences zu ihren Elementen, d.h. falls ein Objekt nur von einer WeakHashMap referenziert wird, behandelt ihn der Garbage Collector als Garbage und wird es bei Bedarf abräumen. 4 Werkzeuge zur Speicheranalyse Es gibt verschiedene Werkzeuge, die verwendet werden können um den Javaspeicher zu analysieren und eventuelle Leaks aufzuspüren. Die wichtigsten dieser Möglichkeiten werden hier kurz beschrieben. Ein Tool zur Speicheranalyse besitzt jeder Java Programmierer, da es Teil der JDK ist. Die Java Virtual Maschine besitzt nämlich eine Option Xrunhprof, welche Informationen zur Speicherbelegung durch die VM in eine Log-Datei hineinschreiben kann. Weitere Informationen zur Benutzung dieser Option erhält man durch Eingabe von java Xrunhprof:help. Eine Spezifikation der erzeugten Daten ist unter http://developer.java.sun.com/developer/onlinetraning/ Programming/JDCBook/perf3.html/ zu finden. Weiter könnte man das Java Virtual Machine Profiling Interface (dokumentiert unter http://java.sun.com/products/jdk/1.2/docs/guide/jvmpi/jvmpi.html) direkt verwenden. Mit diesem Interface ist es möglich eine Vielzahl interner Vorgänge der VMs zu überwachen, unter anderem auch das Allozieren und Abräumen von Objekten. JVMPI ist aber ein natives Interface, um zu den Informationen zu kommen, müssten wir also eine eigene Library oder DLL in 9

4 Werkzeuge zur Speicheranalyse C programmieren. In den meisten Fällen wird sich der Aufwand ein eigenes Speicheranalysetool zu schreiben kaum lohnen. Die mächtigsten Werkzeuge sind die kommerziellen Memory Profiler/Debugger. Solche Profiler können sowohl die Speicherbelegung als auch referenzierte Objekte und Referenzen überwachen. Die gesammelten Daten werden anschliessend grafisch und tabellarisch dargestellt und können in Ruhe analysiert werden. Zu den kommerziellen Memory Profilern gehören unter anderem: Sitraka Software s JProbe Profiler with Memory Debugger Intuitive System s Optimizeit Java Performance Profiler IBM alphaworks JInsight 4.1 Aufspüren von Memory Leaks mit Sitraka JProbe Das Umgehen mit Speicheranalysetools wird hier an einem einfachen Beispiel gezeigt. Unser Beispielprogramm (Listing 4) ist im Wesentlichen ein GUI mit zwei Schaltflächen. Die Erste dient dazu weitere Schaltflächen hinzuzufügen. Mit der Zweiten können diese Schaltflächen wieder gelöscht werden. In diesem Programm hat sich ein Lapsed Listener eingeschlichen. Bei der Methode addbuttontopanel() werden die neu erzeugten Buttons einem Array buttons hinzugefügt. Die removebuttonfrompanel() Methode löscht die Buttons nur vom Panel, jedoch nicht aus dem buttons-array. Bild 3. Screenshot des Beispielprogramms leakexample.java Als Erstes starten wir JProbe, und wählen im Menu Run Profile and debug Java Code. Darauf erscheint das JProbe LaunchPad, hier müssen das Class-File angeben, welches die main- Methode beeinhaltet. Nach dem Betätigen der run-schaltfläche, sind zwei Fenster sichtbar. Das Programmfenster und auch das Runtime Heap Summary von JProbe. In diesem Fenster sehen wir zur Laufzeit, wieviel Speicher unser Java-Programm gerade braucht und auch welche und wie viele Klassen gerade instanziert sind. Dieses Fenster erlaubt es uns auch den GC explizit aufzurufen. Es können ebenfalls sogenannte Checkpoints gesetzt werden. Nach dem Setzen eines Checkpoints erscheint in der Instance Summary -Tabelle eine zusätzliche Spalte mit der Bezeichnung Count Change. In dieser Spalte kann abgelesen werden, ob die Anzahl Instanzen einer Klasse seit dem letzten Checkpoint zu oder abgenommen an. 10

4 Werkzeuge zur Speicheranalyse Bild 4. Runtime Heap Summary nach dem Setzten eines Checkpoints. Beim Betätigen des add-buttons in unserem Testprogramm, steigt der Count Change Wert der Klasse JButton, da wir neue Instanzen von JButton erzeugen. Beim anschliessenden Betätigen des remove-buttons verschwinden zwar die JButtons von unserer Programmoberfläche, der Wert für Count Change bleibt aber gleich, auch nachdem wir den Garbage Collector angeworfen haben. Offensichtlich werden die JButtons nicht mehr angezeigt bleiben aber alle im Speicher. Jprobe kann auch herausfinden an welcher Stelle in unserem Quellcode die Objekte instanziert werden, welche einen eventuellen Leak verursachen. Nach Beenden des Testprogramms, kann im JProbe Hauptfenster der Heap-Schnappschuss ausgewählt und angezeigt werden. Daraufhin erscheint der HeapBrowser. In diesem Fenster kann die Klasse, sowie eine bestimmte Instanz dieser Klasse ausgewählt werden. Nun stehen zu dieser Instanz verschiedene Informationen zur Verfügung, z. B. von wem die Klasse referenziert wurde, wen sie referenzierte, oder die genaue Stelle in unserem Quellcode, wo die Instanz erstellt wurde (siehe auch Bild 4). Sobald bekannt ist an welcher Stelle im Quellcode der Leak entsteht, ist es meist nicht allzu schwierig den Memory Leak zu beheben. Listing 5 im Anhang zeigt eine mögliche Lösung für das vorherige Beispiel mit den leakenden JButtons. Die ungewollten Referenzen sind in diesem Beispiel diejenigen im buttons-array. Nun wählen wir als Datentyp für dieses Array- WeakReferences und fügen die Buttons als ebensolche WeakReferences in das Array ein. Sobald die JButtons vom Panel gelöscht werden, sind sie nur noch durch schwache Referenzen zu erreichen und werden demzufolge als Garbage angeschaut. Analysieren wir LeakExampleFixed.java (Listing 5) mit Jprobe, werden wir sehen, dass beim Betätigen der remove-schaltfläche, die JButtons nicht nur vom Panel sondern auch aus dem Speicher gelöscht werden. Der Count Change -Wert für die JButtons wird also wieder auf 0 zurückfallen. 11

4 Werkzeuge zur Speicheranalyse Bild 4. HeapBrowser mit offenem Quellcode-Fenster In diesem Kapitel wurde ein kurzer Überblick über die Möglichkeiten eines professionellen Speicheranalysetools für Java gegeben. Solche Tools bieten eine Vielzahl weiterer Funktionen, z.b. Laufzeitanalyse der Applikation um herauszufinden welche Klassen oder Vorgänge dafür verantwortlich sind, dass ein Programm zu langsam läuft. Detaillierte Informationen zu allen Funktionen, sowie eine 15 Tage Testversion von JProbe kann unter www.jprobe.com gefunden werden. Schluss Memory Leaks sind nicht nur in C/C++ ein Problem, es gibt sie sehr wohl auch in Java. Viele Memory Leaks in Java sind nur temporär, spätestens nach dem Beenden der Java VM sind sie garantiert weg. Da aber Java Memory Leaks sehr viel Speicher belegen können, müssen vor allem bei langlebigen Applikationen Memory Leaks unbedingt verhindert werden. Java Memory Leaks entstehen in bei definierten Programmiermuster (Patterns), das Kennen dieser Muster, ist der erste Schritt um in Zukunft Memory Leaks zu vermeiden. Falls es doch zu Leaks kommt, gibt es immer noch Werkzeuge wie JProbe, die den Programmierer bei der Fehlersuche unterstützen können. 12

A. Anhang A. Anhang A1. Listings Listing 4. Beispiel für einen Lapsed Loiterer /***************************************************************************** * Copyright (c) 2001, SITRAKA INC. All Rights Reserved. * http://www.sitraka.com * * This file is provided for demonstration and educational uses only. * Permission to use, copy, modify and distribute this file for * any purpose and without fee is hereby granted, provided that the * above copyright notice and this permission notice appear in all * copies, and that the name of Sitraka not be used in advertising * or publicity pertaining to this material without the specific, * prior written permission of an authorized representative of * Sitraka. * * SITRAKA MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY * OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, OR NON-INFRINGEMENT. SITRAKA SHALL NOT BE LIABLE FOR ANY * DAMAGES SUFFERED BY USERS AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. *****************************************************************************/ /* * $RCSfile: LeakExample.java,v $ * $Date: 2001/05/25 07:32:06 $ * $Revision: 1.1.2.1 $ */ package examples.profiler.applications.leakexample; import javax.swing.*; import java.awt.*; import java.awt.event.*; public class LeakExample extends JFrame JPanel panel = new JPanel(); JButton buttons[] = new JButton[100]; int numbuttons = 0; public LeakExample() super("leak Example"); addwindowlistener(new WindowAdapter() public void windowclosing(windowevent e) System.exit(0); ); panel.setlayout(new FlowLayout() ); getcontentpane().add("center", panel); JPanel addremovepanel = new JPanel(); JButton addbtn = new JButton("Add"); addbtn.addactionlistener(new ActionListener() public void actionperformed(actionevent e) addbuttontopanel(); 13

A. Anhang ); addremovepanel.add(addbtn); JButton removebtn = new JButton("Remove"); removebtn.addactionlistener(new ActionListener() public void actionperformed(actionevent e) removebuttonfrompanel(); ); addremovepanel.add(removebtn); getcontentpane().add("north", addremovepanel); public void addbuttontopanel() if ( numbuttons < buttons.length ) JButton btn = new JButton(String.valueOf( numbuttons ) ); buttons[numbuttons++] = btn; panel.add(btn); panel.validate(); panel.repaint(); public void removebuttonfrompanel() if ( numbuttons > 0 ) panel.remove(buttons[--numbuttons]); panel.validate(); panel.repaint(); System.gc(); static public void main(string args[]) JFrame frame = new LeakExample(); frame.setsize(300, 200); frame.setvisible(true); // End of class Listing 5. Das gleiche Beispiel nochmals, der Memory Leak wurde aber durch WeakReferences gelöst. package examples.profiler.applications.leakexample; import javax.swing.*; import java.awt.*; import java.awt.event.*; public class LeakExampleFixed extends JFrame JPanel panel = new JPanel(); java.lang.ref.weakreference buttons[] = new java.lang.ref.weakreference[100]; int numbuttons = 0; public LeakExampleFixed() super("leak Example"); addwindowlistener(new WindowAdapter() public void windowclosing(windowevent e) System.exit(0); ); panel.setlayout(new FlowLayout() ); 14

A. Anhang getcontentpane().add("center", panel); JPanel addremovepanel = new JPanel(); JButton addbtn = new JButton("Add"); addbtn.addactionlistener(new ActionListener() public void actionperformed(actionevent e) addbuttontopanel(); ); addremovepanel.add(addbtn); JButton removebtn = new JButton("Remove"); removebtn.addactionlistener(new ActionListener() public void actionperformed(actionevent e) removebuttonfrompanel(); ); addremovepanel.add(removebtn); getcontentpane().add("north", addremovepanel); public void addbuttontopanel() if ( numbuttons < buttons.length ) JButton btn = new JButton(String.valueOf( numbuttons ) ); buttons[numbuttons++] = new java.lang.ref.weakreference(btn); panel.add(btn); panel.validate(); panel.repaint(); public void removebuttonfrompanel() if ( numbuttons > 0 ) panel.remove(((jbutton)(buttons[--numbuttons]).get())); panel.validate(); panel.repaint(); System.gc(); static public void main(string args[]) JFrame frame = new LeakExampleFixed(); frame.setsize(300, 200); frame.setvisible(true); // End of class 15

A. Anhang A2. Literaturverzeichnis Henry, E.; Lycklama, Ed (2000). How Do You Plug Java Memory Leaks, Dr. Dobb's- Journal. http://www.ddj.com/documents/s=888/ddj0002l Stand: 15.05.2002 Patrick, Jim (2001). Handling Memory leaks in Java programs; IBM developer works. http://www-106.ibm.com/developerworks/java/library/j-leaks Stand: 15.05.2002 Nylund Joel (1999). Memory Leaks in Java Programs; Java Report. http://www.javareport.com/html/from_pages/oldarticles.asp?articleid=165 Stand: 15.05.2002 (2001) JProbe Profiler with Memory Debugger Manual, Sitraka Software 16