3.16 Lebensende eines Objekts
Jedes Objekt wird früher oder später nicht mehr gebraucht und beendet seine Existenz. In diesem Zusammenhang sollten Ressourcen freigegeben werden, die mit dem Objekt zusammenhängen. Die folgenden Abschnitte zeigen, welche Automatismen es dazu gibt.
3.16.1 Garbage Collector
In Visual Basic wird das komplette Speichermanagement im Hintergrund automatisch erledigt. Wenn es keine Möglichkeit mehr gibt, auf ein Objekt zuzugreifen, ist es nicht mehr erreichbar, und der belegte Speicher kann freigegeben werden. Die einfachste Art, die Unerreichbarkeit selbst zu produzieren, besteht darin, alle Referenzen auf das Objekt mit einem neuen Wert zu belegen, zum Beispiel mit Nothing. Bitte beachten Sie, dass es auch indirekte Referenzen gibt, an die Sie vielleicht nicht sofort denken. Zum Beispiel kann jedes Element eines Arrays auch über eine Referenz auf das gesamte Array erreicht werden. Oft wird ein Objekt auch automatisch unerreichbar, zum Beispiel kann auf eine lokale Variable nach Verlassen einer Methode nicht mehr zugegriffen werden, und der Speicher der (nichtstatischen) Variablen kann freigegeben werden. Damit Sie sich nun nicht selbst um die Entsorgung der unerreichbaren Objekte kümmern müssen, läuft parallel zu Ihrer Anwendung ein Programm niederer Priorität, das automatisch aufräumt und »Garbage Collector« genannt wird.
Wenn der Prozessor nicht ausgelastet ist und immer, wenn der Speicher knapp wird, räumt der Müllsammler auf. |
Meistens brauchen (und sollten) Sie sich darum überhaupt nicht kümmern. In seltenen Fällen ist ein Eingriff erwünscht, und die Klasse System.GC stellt die Möglichkeiten in Tabelle 3.28 zur Verfügung (alle Mitglieder sind mit Shared klassengebunden, d. h. der Aufruf erfolgt zum Beispiel mit GC.Collect()):
Art | Beschreibung |
AddMemoryPressure(bytes) |
Kündigt an, dass bald bytes Speicher gebraucht werden. |
Collect([generation, [modus]]) |
Forciert eine Speicherbereinigung. |
CollectionCount(generation) |
Gibt die Anzahl der Speicherbereinigungen zurück. |
GetGeneration(referenz) |
Gibt das relative Alter von referenz zurück. |
GetTotalMemory(aufgeräumt) |
Gibt die Größe des reservierten Speichers zurück. |
KeepAlive(objekt) |
Verhindert das Aufräumen vom Start der Methode, in der der Aufruf steht, bis zum Aufruf (=> besser am Methodenende). |
RemoveMemoryPressure(bytes) |
Informiert, dass nichtverwalteter Speicher freigeworden ist. |
ReRegisterForFinalize(objekt) |
Erlaubt das Ausführen des Destruktors. |
SuppressFinalize(objekt) |
Verhindert das Ausführen des Destruktors. |
WaitForPendingFinalizers() |
Hält das Programm an, bis alle wartenden Destruktoren fertig sind. |
MaxGeneration |
Schreibgeschützte maximale Anzahl von Altersstufen von Objekten. |
Hinweis |
Objekte, die nur über Referenzen erreichbar sind, die mit WeakReference geschaffen wurden, können jederzeit vom Müllsammler freigegeben werden. Solche Referenzen werden manchmal bei einfach wieder zu erzeugenden Objekten eingesetzt. |
3.16.2 Destruktoren
Objekte sind nur so lange nutzbar, wie es eine Referenz gibt, über die das Objekt direkt oder indirekt angesprochen werden kann. Dabei gehen Referenzen durch die Neubelegung »verloren«, zum Beispiel mit Nothing. Verlässt der Programmfluss den Gültigkeitsbereich einer lokalen Variablen mit einer Objektreferenz, wird diese automatisch »ungültig« gemacht (Ausnahmen: die Variable ist mit Static gekennzeichnet oder wird in einer verwendbaren puren Funktion verwendet, siehe Abschnitt 3.9.5, »Funktionsobjekte: Function (λ-Ausdrücke)«). Sowie das Objekt nicht mehr nutzbar ist, kann es zerstört werden. Ob und wann dies passiert wird vom Laufzeitsystem bestimmt und gegebenenfalls automatisch erledigt (siehe Abschnitt 3.16.1, »Garbage Collector«). Bevor der Speicher, den das Objekt belegt, endgültig freigegeben wird, ruft das Laufzeitsystem automatisch eine spezielle Methode auf, den sogenannten Destruktor (wenn er definiert ist). Üblicherweise enthält die Methode die Freigabe von Ressourcen, die nicht vom Laufzeitsystem verwaltet werden. Dieser Destruktor hat immer die folgende Syntax, inklusive der Modifikatoren (optionale Teile sind in eckige Klammern gesetzt und kursive Teile müssen Sie Ihren Bedürfnissen anpassen):
Protected Overrides Sub Finalize()
[<Anweisungen>]
End Sub |
Als Beispiel dient eine Klasse Pirat mit einem Namensfeld zur leichteren Zuordnung von Ausdrucken des Konstruktors, der Finalize-Methode.
'...\Klassendesign\Destruktoren\Destruktor.vb |
Option Strict On
Namespace Klassendesign
Class Pirat
Private name As String
Sub New(ByVal name As String)
Me.name = name
End Sub
Protected Overrides Sub Finalize()
Console.WriteLine("Pirat {0} lebt grade noch.", name)
End Sub
End Class
...
End Namespace
Getestet wird der Destruktor, indem die Referenzen auf ein Objekt vom Typ Pirat neu belegt werden. Die Aufrufe des Müllsammlers GC sind notwendig, um das Aufräumen zu erzwingen. Ohne sie gibt es für das Laufzeitsystem bei einem so kleinen Programm keinen Anlass, nicht mehr erreichbare Objekte zu zerstören. In realen Anwendungen sollten Sie mit solchen Aufrufen sparsam umgehen, da sie recht lange dauern können.
'...\Klassendesign\Destruktoren\Destruktor.vb |
Option Strict On Namespace Klassendesign ... Module Destruktor Sub Test() Dim pirat As Pirat = New Pirat("Störtebecker") Dim legende As Pirat = pirat pirat = Nothing : Console.Write("Pirat vernichtet: ") GC.Collect() : GC.WaitForPendingFinalizers() legende = New Pirat("Hook") : Console.Write("Hook als neue Legende: ") GC.Collect() : GC.WaitForPendingFinalizers() legende = Nothing : Console.Write("Legende vernichtet: ") GC.Collect() : GC.WaitForPendingFinalizers() Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe zeigt, dass die Neubelegung der Referenz auf den Pirat »Störtebecker« diesen nicht vernichtet hat. Erst nachdem auch die Referenz auf die Legende umgebogen wurde, kommt es zur Zerstörung. Da auf den Pirat »Hook« nur eine einzige Referenz existiert, reicht eine einzige Neubelegung zu dessen Zerstörung. Die korrekte Ausgabe der Namen zeigt, dass ein voller Zugriff auf das Objekt möglich ist, während der Destruktor ausgeführt wird.
Pirat vernichtet: Hook als neue Legende: Pirat Störtebecker lebt grade noch. Legende vernichtet: Pirat Hook lebt grade noch.
Einige Punkte sind bei Destruktoren noch zu beachten:
- Objekte, deren Typen Destruktoren (Finalize) enthalten, beanspruchen mehr Rechenzeit bei der Freigabe des Objekts.
- Destruktoren werden im Gegensatz zu Konstruktoren nicht verkettet. Es liegt in Ihrer Verantwortung, den Destruktor der Elternklasse explizit aufzurufen.
- Eine Ausnahme in einer Finalize-Methode beendet das Programm und sollte daher nur verwendet werden, wenn im Fehlerfall eine sofortige Beendigung erwünscht ist.
- Wird in Finalize einer Variablen außerhalb der Klasse eine Referenz auf das in Zerstörung befindliche Objekt zugewiesen, müssen Sie die Methode GC.ReRegisterForFinalize(Me) aufrufen, wenn für das wiedererstandene Objekt bei erneuter Zerstörung der Destruktor aufgerufen werden soll.
Hinweis |
In sehr vielen Fällen ist die Methode Dispose der Schnittstelle IDisposable geeigneter zum Aufräumen als Finalize. |
3.16.3 Dispose und Using
Sollen Aufräumarbeiten automatisch ausgeführt werden, wenn ein Objekt nicht mehr gebraucht wird, bietet sich eine spezielle Syntax an, die automatisch die Dispose()-Methode des Using-Objekts aufruft (optionale Teile stehen in eckigen Klammern, kursive Bezeichner sind frei wählbar):
Using Variable [As IDisposable = New Typ([<Parameter>])] [, <Variablen>]
[<Anweisungen>]
End Using |
Dabei ist zu beachten:
- Der Typ der Variable in Using muss IDisposable implementieren.
- Die optionale Initialisierung kann auch mit As New Typ([<Parameter>]) erfolgen.
- Nur eine Deklaration mit As ohne New ist nicht erlaubt.
- Wenn initialisiert wird, ist die Variable schreibgeschützt.
- Dispose() wird auch im Fall einer Ausnahme aufgerufen.
- Der Aufruf von Dispose() erfolgt auch dann korrekt, wenn eine Using-Referenz (ohne Initialisierung) neu belegt wird.
- Using v1, v2 : End Using ist äquivalent zu Using v1 : Using v2 : End Using : End Using.
Das folgende Codefragment zeigt eine Klasse Festsaal, die die Schnittstelle IDisposable implementiert. Der auskommentierte Aufruf in Dispose() kann benutzt werden, um ein doppeltes Aufräumen zu verhindern. In der Methode Test() werden in zwei Using-Blöcken je ein vor und ein in Using initialisiertes Objekt vom Typ IDisposable verwendet. Um den Effekt einer Nullreferenz zu testen, wird das vorher initialisierte Objekt neu belegt. Die korrespondierende Anweisung im zweiten Using-Block ist auskommentiert, da die Variable durch die Initialisierung in der Using-Einleitung schreibgeschützt ist und der Compiler einen Fehler melden würde. Der zweite Block testet den Effekt einer Ausnahme.
'...\Klassendesign\Destruktoren\Using.vb |
Option Strict On Namespace Klassendesign Class Festsaal : Implements IDisposable Sub Dispose() Implements IDisposable.Dispose Console.WriteLine("Sauber machen und aufräumen.") 'GC.SuppressFinalize(Me) End Sub End Class Module Freigeben Sub Test() Dim saal As New Festsaal() Using saal saal = Nothing End Using Try Using kneipe As IDisposable = New Festsaal() 'kneipe = Nothing 'Compilerfehler!! Throw New Exception("Ausnahme in Using") End Using Catch ex As Exception Console.WriteLine("Ausnahme {0}", ex.Message) End Try Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe bestätigt, dass auch im Fall von Nullreferenzen oder Ausnahmen die Methode Dispose() aufgerufen wird:
Sauber machen und aufräumen. Sauber machen und aufräumen. Ausnahme Ausnahme in Using
Hinweis |
Die Methode Dispose() sollte sicherstellen, dass ein wiederholter Aufruf unschädlich ist, zum Beispiel über eine boolesche Variable als Markierung, dass der Aufruf bereits stattgefunden hat. Diese kann auch in anderen Methoden der Klasse verwendet werden, um zu verhindern, dass Zugriffe auf ein bereits freigegebenes Objekt erfolgen. |
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.