Wenn ein Objekt vom Garbage Collector zur Entsorgung markiert wurde, dann ruft der Garbage Collector in der Regel die Finalize-Methode des Objekts auf, bevor er den Speicher des Objekts endgültig freigibt. Schon die Object-Klasse hat Finalize implementiert, und da alle Klassen von Object abgeleitet sind, hat jedes Objekt in .NET eine Finalize-Methode.
Die Finalize-Methode in ihrer Grundimplementierung von Object macht überhaupt nichts. Sie ist in erster Linie einfach nur vorhanden, und das bedeutet, dass ein Objekt die Finalize-Methode überschreiben muss, wenn es eine eigene Funktionalität für seine »Entsorgungsvorbereitung« implementieren will.
Der Finalizer ist also im Prinzip der (leider eben nicht deterministische) Destruktor von .NET Klassen.
Die Begleitdaten zum folgenden Beispiel können Sie hier herunterladen.
Module mdlMain
Sub Main()
Dim locTest As New Testklasse("Erste Testklasse")
Dim locTest2 As New Testklasse("Zweite Testklasse")
locTest = Nothing
locTest2 = Nothing
'GC.Collect()
'einen Moment (eine Sekunde, genaugenommen) warten
Threading.Thread.Sleep(1000)
Console.WriteLine("Beide Objekte sind nun nicht mehr in Verwendung!")
End Sub
End Module
Class Testklasse
Private myName As String
Sub New(ByVal Name As String)
myName = Name
End Sub
Protected Overrides Sub Finalize()
MyBase.Finalize()
Console.WriteLine(Me.myName & " wurde entsorgt")
End Sub
End Class
Tipp: Starten Sie dieses Programm mit (Strg)+(F5), also ohne Debuggen, damit das Konsolenfenster nach dem Beenden des Programms nicht einfach wieder verschwindet. Sie würden das Ergebnis sonst nicht sehen können.
Wenn Sie dieses Programm starten, dann stellen Sie fest, dass die Meldung »Beide Objekte sind nun nicht mehr in Verwendung!« zuerst ausgegeben wird. Erst anschließend erscheinen die Texte, die anzeigen, dass die beiden verwendeten Objekte finalisiert worden sind:
Beide Objekte sind nun nicht mehr in Verwendung!
Zweite Testklasse wurde entsorgt
Erste Testklasse wurde entsorgt
Die Ursache dafür liegt schon fast auf der Hand: Der Garbage Collector arbeitet in diesem Programm nicht, während es läuft, und bei einem Speicheraufkommen von nur ein paar Byte hat er dafür auch gar keinen Grund. Das Finalisieren der Objekte findet dennoch statt, und zwar beim Beenden der Anwendung. Zu diesem Zeitpunkt ist die letzte Zeile des eigentlichen Programms aber längst verarbeitet worden; in diesem Fall war es die Codezeile, die den Meldungstext auf den Bildschirm ausgegeben hat. Eine andere Ausgabe erscheint, wenn Sie das Kommentarzeichen vor der Zeile
'GC.Collect()
weglassen. Starten Sie das Programm anschließend, ändert sich die Ausgabe in:
Zweite Testklasse wurde entsorgt
Erste Testklasse wurde entsorgt
Beide Objekte sind nun nicht mehr in Verwendung!
Wichtig: Im Beispielprogramm habe ich die Console-Klasse in der Finalize-Methode wie selbstverständlich verwendet. Machen Sie das nicht. Mal ganz davon abgesehen, dass Sie in der Finalize-Methode keine wie auch immer gearteten Bildschirmausgaben mehr machen sollten, um die Finalisierung so schnell wie möglich hinter sich zu bringen, können Sie sich auch nicht sicher sein, ob Objekte, die Sie verwenden, zu diesem Zeitpunkt noch bestehen.
Wann Finalize nicht stattfindet
Unter Umständen verursachen Sie beim Verwenden bestimmter Objekte in Finalize, dass neue Objekte während des Vorgangs entstehen, die ihrerseits wiederum neue Objekte anlegen, usw. Diese Objekte müssen natürlich anschließend ebenfalls finalisiert werden. Im schlimmsten Fall lösen Sie damit eine solch enorme Kaskade von neuen Objekten aus, dass diese alle niemals finalisiert werden könnten. Doch ihre Anwendung hängt sich deshalb nicht auf, denn .NET Framework hat zu diesem Zweck ein paar Sicherheitsmaßnahmen vorgesehen.
Die Beispieldateien zum folgenden zweiten Beispiel können Sie hier dowloaden.
Öffnen Sie dort die entsprechende Projektmappe (.SLN-Datei). Denken Sie daran, dass dieses Beispiel zeigt, was Sie in Ihren Anwendungen niemals machen sollten!
Module mdlMain
Sub Main()
Dim locTest As New Testklasse("Testklasse")
End Sub
End Module
Class Testklasse
Private myName As String
Sub New(ByVal Name As String)
myName = Name
End Sub
Sub WriteText()
Console.Write(myName)
End Sub
Protected Overrides Sub Finalize()
MyBase.Finalize()
Dim locTemp As New Testklasse("locTemp")
WriteText()
Console.WriteLine(" wurde entsorgt")
End Sub
End Class
Wenn Sie dieses Programm starten, wird eine Reihe von Meldungen ausgegeben.Finalize selbst legt dabei dummerweise eine neue Instanz von Testklasse an, um eine Meldung auszugeben. Diese Instanz muss natürlich ebenfalls finalisiert werden, und sie legt ihrerseits wieder eine neue Testklasse-Instanz an usw. Der GC erkennt nach einer Weile, dass der Finalisierungsprozess genau das Gegenteil von dem bewirkt, was er eigentlich bewirken sollte, es entstehen nämlich immer mehr Objekte, und der Speicherbedarf wächst und wächst. Er bricht das Finalisieren nach ein paar Sekunden schlicht und ergreifend ab.
Ein weiteres schlechtes Beispiel ist das Folgende (wenn Sie es unbedingt selbst probieren wollen: Sie können es hier downloaden. Es legt zwar keine Unmenge von neuem Speicher an, verbraucht für den Finalisierungsprozess aber einfach zu viel Zeit. Der GC wird nach vergleichsweise kurzer Zeit ungeduldig und bricht den gesamten Finalisierungsprozess ebenfalls wieder ab. Das im Beispielprogramm zuerst deklarierte Objekt bekommt keine Chance mehr, finalisiert zu werden.
Module mdlMain
Sub Main()
'"Normales" Objekt, könnte problemlos finalisiert werden.
Dim locTest1 As New Testklasse(False, "Erstes Testobjekt")
'Der Störenfried, da Warteschleifenflag gesetzt.
Dim locTest2 As New Testklasse(True, "Zweites Testobjekt")
End Sub
End Module
Class Testklasse
'Dieses Flag steuert den Einstieg in die Warteschleife.
Private myWaitInFinalize As Boolean
'Eine Eigenschaft zum Unterscheiden von Klasseninstanzen
Private myName As String
'Flag fürs Warten und den Namen definieren.
Sub New(ByVal WaitInFinalize As Boolean, ByVal Name As String)
myWaitInFinalize = WaitInFinalize
myName = Name
End Sub
Protected Overrides Sub Finalize()
MyBase.Finalize()
'Nur wenn das Flag bei New gesetzt
'wurde, in die Warteschleife springen.
If myWaitInFinalize Then
Dim locSecs As Integer
Dim lastSec As Integer
lastSec = Now.Second
Do
'Jede Sekunde eine Meldung ausgeben.
If lastSec <> Now.Second Then
lastSec = Now.Second
locSecs += 1
Console.WriteLine("Warte bereits {0} Sekunden", locSecs)
'Nach 60 Sekunden wäre Schluss.
If locSecs = 60 Then Exit Do
End If
Loop
End If
'Erfolgreich finalisiert --> Meldung ausgeben
Console.WriteLine("Objekt {0} wurde finalisiert!", myName)
End Sub
End Class
Für den Fall, dass Sie eine eigene Finalisierungslogik in Ihren Klassen implementieren müssen – und bitte: immer nur mithilfe der Implementierung von IDisposable wie im nächsten Abschnitt beschrieben –, sind also folgende Punkte wichtig:
- Achten Sie darauf, dass der Finalisierungsprozess nicht nur so schnell wie möglich erledigt ist, sondern wirklich kaum Zeit beansprucht
- Stellen Sie sicher, dass Sie auf keinen Fall neue Instanzen von irgendwelchen Objekten innerhalb des Finalisierungsprozesses erstellen
Es gibt übrigens Objekte in .NET Framework, deren Vorhandensein die CLR auch noch zum Finalisie-rungszeitpunkt garantiert. Da Sie Ausgaben bei der Finalisierung wahrscheinlich nur zu Testzwecken machen werden, verwenden Sie dafür besser die Debug-Klasse. Diese Klasse stellt ebenfalls eine Write- bzw. WriteLine-Methode zur Verfü-gung, hat aber gegenüber Console entscheidende Vorteile: Das Vorhandensein der Klasse zum Finalisie-rungszeitpunkt ist garantiert, und Ausgaben erfolgen darüber hinaus nur in einen so genannten Trace-Listener. Soweit zur Finalisierung von Objekten durch .NET Framework. Finalize ist allerdings eine Methode, die ausschließlich durch .NET Framework aufgerufen werden darf. Was ist aber, wenn Sie Objekte erstellen wollen, die der Benutzer selbst – quasi – schließen oder freigeben will? .NET Framework bietet dazu ein Schnittstellenmuster über die so genannte IDisposable- Schnittstelle an. Der nächste Abschnitt verrät mehr über dieses Thema.
Mehr zu diesem Thema in: Visual Basic 2010 das Entwicklerbuch von Klaus Löffelmann, erhältlich bei Amazon auch auf Englisch, und bei Microsoft Press. Möchten Sie Klaus Löffelmann oder das ActiveDevelop-Team für professionelle Software-Entwicklungsprojekte (Migration, C#, Visual Basic.NET) buchen, informieren Sie sich auf www.activedevelop.de
klauslo am 03. Dezember 2011
b7132cb7-be99-4bec-9eff-55da7f5832a1|1|5.0
Tags: |
Categories: Garbage Collector