I'm using Delphi 7.
I have two units containing Form1
and Form2
. The secondary form will be called many times during some process, and I am very worried about memory usage.
When I start the program, memory usage is around 2.1 MB. When Form2
is called, memory grows to 2.9 MB. After this process, I close Form2
and call it again to simulate regular usage, and memory grows to 3.1 MB, call again and memory grows to 3.4 MB, 3.6 MB, 3.8 MB, etc.
Memory usage is the main issue.
Form1
is calling Form2
like this:
uses
Unit2;
...
private
{ Private declarations }
FChild : TForm2;
...
FChild := TForm2.Create(nil);
try
FChild.ShowModal;
finally
FChild.Free;
end;
Inside Unit2
:
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
Am I doing something wrong? Is there a better solution?
Please, this not a simple question, because this program will be running 24 hours, and the second form will be called many times. This means that sometimes this program will freeze the computer.
I included FASTMM4 inside project:
program Project1;
uses
FastMM4,
Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
begin
FullDebugModeScanMemoryPoolBeforeEveryOperation := True;
SuppressMessageBoxes:=False;
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
This program is reading a fingerprint.
Unit1:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Unit2;
type
TForm1 = class(TForm)
btnForm2: TButton;
btnReselease: TButton;
procedure btnForm2Click(Sender: TObject);
procedure btnReseleaseClick(Sender: TObject);
private
{ Private declarations }
FChild : TForm2;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btnForm2Click(Sender: TObject);
begin
FChild := TForm2.Create(nil);
try
FChild.ShowModal;
finally
FChild.Free;
end;
end;
procedure TForm1.btnReseleaseClick(Sender: TObject);
begin
if FChild <> nil then
begin
FreeAndNil(FChild);
end;
end;
end.
Unit2:
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, pngimage, ExtCtrls, Grids, XMLIntf, XMLDoc,
ComCtrls, CPort;
type
TForm2 = class(TForm)
btnSyncFP: TBitBtn;
btnExitFP: TBitBtn;
btnDeleteFP: TBitBtn;
btnCaptureFP: TBitBtn;
ComImage: TImage;
FPGrid: TStringGrid;
prgBar: TProgressBar;
lblbar: TLabel;
ComPortA: TComPort;
ComDataPacket1: TComDataPacket;
procedure LoadUsers2;
procedure FormCreate(Sender: TObject);
procedure ComPortAAfterOpen(Sender: TObject);
procedure ComPortAAfterClose(Sender: TObject);
procedure btnExitFPClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
end;
var
// Form2: TForm2;
L1, L2: TStringList;
implementation
{$R *.dfm}
procedure TForm2.LoadUsers2;
var
XML : IXMLDOCUMENT;
CurNode, CNode : IXMLNODE;
i : Integer;
begin
XML := NewXMLDocument;
XML.LoadFromFile('usuario.xml');
XML.Active := True;
CurNode := XML.DocumentElement.ChildNodes[0]; // users
FPGrid.RowCount := CurNode.ChildNodes.Count+1;
prgBar.Min := 0;
prgBar.Max := CurNode.ChildNodes.Count-1;
lblBar.Caption := 'Carregando usuários...';
for i := 0 to CurNode.ChildNodes.Count-1 do
begin
CNode := CurNode.ChildNodes[i]; // groups
with CNode do
begin
FPGrid.Cells[2,i+1] := Attributes['group'];
FPGrid.Cells[1,i+1] := Attributes['id'];
FPGrid.Cells[0,i+1] := Attributes['name'];
FPGrid.Cells[3,i+1] := Attributes['fingerID'];
FPGrid.Cells[4,i+1] := Attributes['level'];
FPGrid.Cells[5,i+1] := Attributes['status'];
end;
if FPGrid.Cells[3,i+1]<>'' then L1.Add(FPGrid.Cells[3,i+1]);
prgBar.Position := i;
end;
XML := nil;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
LoadUsers2;
with FPGrid do
begin
Font.Name := 'Tahoma';
Font.Size := 12;
ColCount := 4;
Cells[0,0] := 'Name'; Cells[1,0] := 'ID';
Cells[2,0] := 'Group'; Cells[3,0] := 'Read ID';
Cells[4,0] := 'Level'; Cells[5,0] := 'Status';
ScrollBars := ssVertical;
Options := Options + [goRowSelect];
end;
ComPortA.Open;
end;
procedure TForm2.ComPortAAfterOpen(Sender: TObject);
begin
ComImage.Picture.LoadFromFile('conn_on.png');
end;
procedure TForm2.ComPortAAfterClose(Sender: TObject);
begin
ComImage.Picture.LoadFromFile('conn_off.png');
end;
procedure TForm2.btnExitFPClick(Sender: TObject);
begin
ComPortA.Close;
Close;
end;
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
end.
I know that Task Manager is the best to verify leak memory, but after few hours, the program is growing faster and not realeasing memory.
I let the program running all night and memory is backing, but there is a long time not enough as user usage.
No, apart from a potential error where the form may be "Freed" twice, you appear to be doing everything correctly, assuming that your Form2 implementation does not itself introduce memory leaks.
I am assuming that you are monitoring "memory use" by using Task Manager. If so, this is highly misleading.
Your application is managing its memory using the Delphi heap manager. This heap manager will allocate memory (requesting it from the OS) as required but when it is freed it is not immediately returned to the system (OS), but simply marked as no longer in use. Then, when memory is required by the application in the future, the heap manager can "recycle" this unused memory instead of having to go back to the OS to ask for more memory (which is a relatively "expensive" operation).
However, the way that the heap manager determines whether unused memory can be recycled to satisfy a new request can mean that memory that could potentially be recycled may not be, for example as the result of fragmentation.
Imagine your application allocated 500 bytes of memory then a further 100 bytes and then another 500 bytes:
[1] [used] 500 bytes
[2] [used] 100 bytes
[3] [used] 500 bytes
Imagine then that the two 500 bytes blocks are freed, making them available for re-use.
[1] [free] 500 bytes
[2] [used] 100 bytes
[3] [free] 500 bytes
You might think that a request for 1000 bytes (or even 600, 700, 800 bytes etc) would be able to use this "recyclable" memory.
But that request for 1000 bytes requires a single, contiguous block, and with that 100 byte block still in use, those two 500 byte blocks can only be used for requests for (a maximum of) 500 bytes each. So, a request for 1000 bytes has to be satisfied by allocating a new 1000 byte block:
[1] [free] 500 bytes
[2] [used] 100 bytes
[3] [free] 500 bytes
[4] [used] 1000 bytes
Your application is still only "using" 1100 bytes, but to do so, 2100 bytes have been allocated from the OS.
The net result of all this is that memory "use" can appear to grow in Task Manager when in fact what is really happening is that your application is simply "holding on" to allocated memory it is no longer actually using, just in case it might need it in the future.
If the OS reached a point where it needed memory back, then all processes would be asked to relinquish such memory, and your application would be no different.
You can simulate this by minimising and restoring your application. Unless your application is genuinely using all of the memory currently allocated, you should see a drop in memory use. This drop might be slight or it could be significant, depending on the memory usage profile of your application.
When a (Delphi) application is minimized it will return some/as much memory to the system, on the basis that if the user has minimized it then it is now a "backgrond" process which is not likely to need to have any demands for memoy in the near future.
You can trigger this behaviour with some code in your application OnIdle event, but doing so is mostly pointless. It may give the impression of reduced memory use in Task Manager, but will potentially reduce performance of your application and doesn't actually reduce your memory usage.
The Delphi runtime has always supported the ability to replace the application heap manager with alternative implementations.
A popular one (which has been adopted as the new standard since Delphi 2006) is FastMM. This implements strategies that avoid or reduce memory fragmentation and provides other performance and debugging improvements (it can be configured to report memory leaks and incorrectly used references to destroyed objects, for example).
FastMM is open source and can be used even in a Delphi 7 application. All you need to do is download the source and add FastMM as the first unit used in your project dpr file.