致电表格并更正发布方式?内存使用量

时间:2017-07-06 19:26:42

标签: delphi delphi-7

我使用Delphi 7。

我有两个包含Form1Form2的单元。在某些过程中,次要表单将被多次调用,我非常担心内存使用情况。

当我启动程序时,内存使用量约为2.1 MB。调用Form2时,内存增长到2.9 MB。在此过程之后,我关闭Form2并再次调用它来模拟常规使用,内存增长到3.1 MB,再次调用,内存增长到3.4 MB,3.6 MB,3.8 MB等。

内存使用是主要问题。

Form1正在调用此Form2

uses
  Unit2;

...

private
  { Private declarations }
  FChild : TForm2;
...      

FChild := TForm2.Create(nil);
try
  FChild.ShowModal;
finally
  FChild.Free;
end;

内部Unit2

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;

我做错了吗?有更好的解决方案吗?

请注意,这不是一个简单的问题,因为这个程序将运行24小时,第二个表单将被多次调用。这意味着有时这个程序会冻结计算机。

我在项目中包含了FASTMM4:

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.

这个程序正在读取指纹。

1单元:

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.

我知道任务管理器是最好的验证泄漏内存,但几个小时后,程序增长得更快,而不是重新释放内存。

我让程序整夜运行并且内存正在备份,但是用户使用时间很长。

3 个答案:

答案 0 :(得分:5)

不,除了表单可能被“释放”两次的潜在错误之外,您似乎正在正确地执行所有操作,假设您的 Form2 实现本身不会引入内存泄漏。

我假设您正在使用任务管理器监视“内存使用”。如果是这样,这是极具误导性的。

您的应用程序正在使用Delphi堆管理器管理其内存。此堆管理器将根据需要分配内存(从OS请求它),但是当它被释放时,它不会立即返回到系统(OS),而只是标记为不再使用。然后,当将来应用程序需要内存时,堆管理器可以“回收”这个未使用的内存,而不必返回操作系统以请求更多内存(这是一个相对“昂贵”的操作)。

但是,堆管理器确定是否可以回收未使用的内存以满足新请求的方式可能意味着可能无法回收的内存可能不会,例如碎片的结果。

想象一下,您的应用程序分配了500个字节的内存,然后再分配100个字节,然后再分配500个字节:

[1] [used] 500 bytes
[2] [used] 100 bytes
[3] [used] 500 bytes

想象一下,释放了两个500字节的块,使它们可以重复使用。

[1] [free] 500 bytes
[2] [used] 100 bytes
[3] [free] 500 bytes

您可能认为1000字节(甚至600,700,800字节等)的请求将能够使用此“可循环”内存。

但是对1000字节的请求需要一个连续的块,并且该100字节块仍在使用中,这两个500字节块只能用于每个(最多)500字节的请求。因此,必须通过分配 new 1000字节块来满足1000字节的请求:

[1] [free] 500 bytes
[2] [used] 100 bytes
[3] [free] 500 bytes
[4] [used] 1000 bytes

您的应用程序仍然只“使用”1100个字节,但为此,已从操作系统分配了2100个字节。

所有这一切的最终结果是内存“使用”在任务管理器中看起来会增长,而事实上真正发生的事情是你的应用程序只是“坚持”它已经不再使用的已分配内存,只是如果将来可能需要它。

如果操作系统达到需要内存的程度,则会要求所有进程放弃此类内存,并且您的应用程序也不例外。

您可以通过最小化和恢复应用程序来模拟这一点。除非您的应用程序真正使用当前分配的所有内存,否则您应该看到内存使用量下降。此丢弃可能很小,也可能很大,具体取决于应用程序的内存使用情况。

当(Delphi)应用程序被最小化时,它将向系统返回一些/尽可能多的内存,基于如果用户已将其最小化,那么它现在是一个“背景”过程,其可能不需要具有在不久的将来对memoy的任何要求。

您可以使用应用 OnIdle 事件中的某些代码触发此行为,但这样做大多没有意义。它可能会在任务管理器中提供减少内存使用的印象,但可能会降低应用程序的性能,并且实际上不会减少内存使用量。

我该怎么办?

Delphi运行时始终支持使用其他实现替换应用程序堆管理器。

一个受欢迎的(自Delphi 2006以来被采纳为新标准)是FastMM。这实现了避免或减少内存碎片的策略,并提供了其他性能和调试改进(例如,它可以配置为报告内存泄漏和错误使用的对已销毁对象的引用)。

FastMM是开源的,甚至可以在Delphi 7应用程序中使用。您需要做的就是下载源代码并添加 FastMM 作为项目 dpr 文件中使用的第一个单元。

答案 1 :(得分:1)

不要在同一个对象上同时使用Action := caFreeFree()。使用其中一个,而不是两个。对Free()的调用实际上会取消Action:=caFree,使其变得多余。

但这不是你问题的原因。

您显示的代码通常很好,Form2可以正确地从内存中释放。您没有考虑到的是Delphi的内存管理器根本不会释放释放的内存回到操作系统,它会保留它并重新使用它以用于将来的分配。如果您使用任务管理器来跟踪内存使用情况,那么这不是诊断内存泄漏的好工具,因为它没有Delphi如何缓存释放内存的概念。

然而,话虽如此,你所描述的增长听起来更像是内存碎片,而不是内存泄漏。但是很难说肯定,因为你没有在Form2上显示所有内容,或提供Minimal, Complete, and Verifiable example来重现问题。

无论如何,Delphi 7的默认内存管理器通常效率不高。这就是为什么Borland最终在Delphi 2006中切换到FastMM的原因。您可以尝试用新的内存管理器替换默认的内存管理器,看看它是否有帮助。

答案 2 :(得分:0)

使用FastMM。请参阅article并添加FastMM。

如果可以,使用完全启用的FastMM诊断创建单独的构建配置并运行它i。即在合并到掌握git分支之前。我使用这种策略并大大提高了我的代码质量,没有任何缺点(FastMM诊断对于编译器来说有点重要,但我不经常运行它们,所以没关系。)