Search code examples
delphidelphi-7

Call a Form and correct way to release? memory usage


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.


Solution

  • 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.

    What Can I Do About It ?

    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.