|
Delphi 8 und der Garbage Collector
Frage: Ist es möglich dass der Garbage Collector von .NET nicht richtig funktioniert? Wenn ich Formulare auf und zu mache, benötigt das Programm immer mehr Speicher, der erst beim Beenden meiner mit Delphi 8 kompilierten VCL.NET-Anwendung freigegeben wird. In dem geöffneten Formular werden Grafiken geladen und angezeigt, wobei ich das Formular am Ende über FormOjbect:=nil freigebe.
Antwort: Der Garbage Collector (GC) arbeitet im Alltag stabil und zuverlässig. Allerdings kümmert sich der GC nur um den managed Code, alle direkten Allozierungen von nativen Win32-Handles etc. müssen wie gehabt in eigener Regie freigegeben werden. Dieses Hintergründe treten bei einer VCL.NET-Anwendung deutlicher hervor als bei einer FCL-Anwendung, da die VCL.NET intern sehr häufig über P/Invoke direkt das Win32-API bemüht. Wenn der GC mit Hintergrund-Modus (siehe .NET Framework Configuration | Eigenschaftn von My Computer | Garbage collection mode "Für Benutzeranwendungen im Hintergrund ausführen") ausgeführt wird, läuft er unter Last in der Regel erst dann an, wenn eine neue Speicheranforderung nicht genug freien Speicherplatz vorfindet. Dann allerdings rattert die Festplatte erst einmal längere Zeit. Wenn man dieses Verhalten nicht möchte, muss man entweder den GC im Vordergrund betreiben oder freiwillig die nicht mehr benötigten (größeren) Objekte mit Dispose (FCL) beziehungsweise Release oder Free (VCL.NET) freigeben.
Der folgende Test demonstriert das Verhalten. Bei 1. Test mit einem leeren VCL.NET-Formular, auf dem nur eine TTimer-Komponente liegt, ist kein Hochlaufen des Working Set des Prozesses zu beobachten (nach 100 Aufrufen ist das Working Set des Prozesses nur um 4 kByte größer geworden). Auch beim 2. Test mit dem Grafik-Formular pendelt das Working Set zwischen 2 stabilen Grenzen. Allerdings lege ich aber freiwillig durch den Relaese-Aufruf den Zeitpunkt des Freigebens der erzeugten Formular-Instanz selbst fest, so dass der Speicherverbrauch des Prozesses meiner Testanwendung erst gar nicht hochläuft. Wird der Aufruf von Form3.Release jedoch durch Form3 := nil ersetzt, ballert man sich das Working Set des Prozesses bei 100 Aufrufen des Grafik-Formulars um 70 MByte zu. Es ist somit auch unter .NET in jedem Fall sinnvoll, derart große Objekte freiwillig explizit (und somit zu einem definierten Zeitpunkt) freizugeben, wenn man derartige Zuwächse nicht möchte.
a) Hauptformular
unit FormMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Borland.Vcl.StdCtrls, System.ComponentModel,
Borland.Vcl.ComCtrls;
type
TForm1 = class(TForm)
StatusBar1: TStatusBar;
EditCount: TEdit;
Label1: TLabel;
ButtonFormEmpty: TButton;
ButtonFormGrafik: TButton;
procedure ButtonFormEmptyClick(Sender: TObject);
procedure ButtonFormGrafikClick(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form1: TForm1;
implementation
uses FormEmpty, FormGrafik;
{ *.nfm}
procedure TForm1.ButtonFormEmptyClick(Sender: TObject);
var
i, iMax : Integer;
begin
iMax := Convert.ToInt32(EditCount.Text);
for i := 1 to iMax do
begin
Form2 := TForm2.Create(nil);
try
Form2.ShowModal;
finally
Form2.Release;
end;
StatusBar1.SimpleText := i.ToString;
Application.ProcessMessages;
end;
end;
procedure TForm1.ButtonFormGrafikClick(Sender: TObject);
var
i, iMax : Integer;
begin
iMax := Convert.ToInt32(EditCount.Text);
for i := 1 to iMax do
begin
Form3 := TForm3.Create(nil);
try
Form3.ShowModal;
finally
Form3.Release;
end;
StatusBar1.SimpleText := i.ToString;
Application.ProcessMessages;
end;
end;
end.
b) aufgerufenes leeres Formular
unit FormEmpty;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, System.ComponentModel, Borland.Vcl.ExtCtrls;
type
TForm2 = class(TForm)
Timer1: TTimer;
procedure Timer1Timer(Sender: TObject);
procedure FormShow(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form2: TForm2;
implementation
{ *.nfm}
{ TForm2 }
procedure TForm2.Timer1Timer(Sender: TObject);
begin
Close;
end;
procedure TForm2.FormShow(Sender: TObject);
begin
Timer1.Interval := 100;
Timer1.Enabled := True;
end;
end.
c) aufgerufenes Grafik-Formular lädt eine 385 kByte-Bitmap
unit FormGrafik;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Borland.Vcl.ExtCtrls, System.ComponentModel;
type
TForm3 = class(TForm)
Image1: TImage;
Timer1: TTimer;
procedure FormShow(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form3: TForm3;
implementation
{ *.nfm}
procedure TForm3.FormShow(Sender: TObject);
begin
Timer1.Interval := 100;
Timer1.Enabled := True;
Image1.Picture.LoadFromFile('C:\Temp\D8TEST.bmp');
end;
procedure TForm3.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := False;
Close;
end;
end.
|