2

我的问题是关于调试似乎是一场噩梦的内存泄漏。
在我的应用程序中有一个简单的类派生自TObject. 该类的所有对象都存储在派生自的类的集合/列表中TObjectList

type
  TOffer = class(TObject)
    Item: string;
    Price: string;
    Id: string;
  end;

  TOffers = class(TObjectList<TOffer>)
  protected
    procedure SetOffer(I: Integer; AOffer: TOffer);
    function GetOffer(I: Integer): TOffer;
  public
    property Offers[I: Integer]: TOffer read GetOffer write SetOffer
  end;

使用场景:
爬虫下载商品,解析并保存到对象集合中。这种方法似乎很方便,因为我可以稍后引用对象(填充网格/列表,将它们写入文件等)

问题是正确处理对象以避免内存泄漏。该应用程序在启动时分配 ~4Mb 内存,但处理后 ~12k 提供它吞噬 32Mb。进程完成后未正确处理的对象/变量导致的泄漏。

ReportMemoryLeaksOnShutdown显示可怕的数字,但关键是 - 我不知道在哪里看以及如何正确调试该死的东西。

var MyString: string另一个例子是也需要适当处理的变量!这对我来说是一种洞察力:) 我认为每个过程/函数都会自动管理范围外变量的垃圾收集。

报价列表由一个函数创建:

function GetOffersList: TOffers;
begin
  Result := TOffers.Create;
  while not rs.EOF do
  begin
    Offer := TOffer.Create;
    try
       // here come collected offer attributes as variables of type string:
        Order.Item := CollectedOfferItem;
        Order.Price := CollectedOfferPrice;
        Order.Id := CollectedOfferId;
        Result.Add(Offer);
    finally
        Offer := nil;
    end;
  end;
end;

然后我直接将这些报价作为一个集合来处理。关键是我希望这个应用程序 24/7 运行,所以正确的资源处理是必须的。

  • 如何正确处置上述类型的对象?
  • 我应该考虑管理对象/对象列表的其他技术吗?
  • 如何正确处理类型变量string
  • 您能否建议有关在 Delphi 中对抗内存泄漏的好读物?

谢谢你。

4

2 回答 2

8

默认情况下,当您创建一个对象时,您将成为它的所有者。只要您是所有者,您就有责任释放它。以下是一些常见的模式:

1.局部变量

对于在方法中创建且仅在本地引用的对象,您可以使用 try/finally 模式:

Obj := TMyClass.Create;
try
  ... use Obj
finally
  Obj.Free;
end;

2. 另一个对象拥有的对象

通常在构造函数中创建并在析构函数中销毁。在这里,您有一个拥有对象的成员字段,其中包含对拥有对象的引用。您需要做的就是调用Free拥有类析构函数中的所有拥有对象。

3. 自有 TComponent

如果使用TComponent创建一个或派生类Owner,则该所有者将销毁该组件。你不需要。

4. TObjectList 或类似的 OwnsObjects 设置为 True

您在问题中显示了这种模式。您创建一个TObjectList<T>并且默认情况下OwnsObjectsTrue. 这意味着当您将成员添加到容器时,容器将承担所有权。从那时起,容器承担销毁其成员的责任,而您不必这样做。但是,仍然必须有人破坏容器。

5. 引用计数的接口对象

常见的例子是从TInterfacedObject. 接口引用计数管理生命周期。您无需销毁该对象。

6. 创建并返回新实例的函数

这是走向更棘手的一端。值得庆幸的是,这是一种相当罕见的模式。这个想法是该函数将一个新实例化和初始化的对象返回给调用者,然后调用者承担所有权。但是当函数仍在执行时,它是所有者并且必须防御异常。通常,代码如下所示:

function CreateNewObject(...): TMyClass;
begin
  Result := TMyClass.Create;
  try
    Result.Initialize(...);
  except
    Result.Free;
    raise;
  end;
end;

这必须是一个带有调用Free和重新引发的异常处理程序,因为代码无法使用 finally。调用者会这样做:

Obj := CreateNewObject(...);
try
  ....
finally
  Obj.Free;
end;

查看问题中的代码,这似乎同时使用了我列表中的第 4 项和第 6 项。但是,请注意您的实现GetOffersList不是异常安全的。但没有迹象表明这是问题所在。GetOffersList调用的代码未能破坏容器似乎是合理的。

你为什么要泄漏字符串?好吧,字符串是托管对象。它们被引用计数,您无需采取明确的行动来销毁它们。但是,如果它们包含在其他类中,其实例被泄露,则包含的字符串也会被泄露。因此,专注于修复对象的泄漏,您将处理字符串泄漏。

对于它的价值,对我来说,TOffer感觉更像是一种值类型而不是引用类型。它没有方法并且包含三个简单的标量值。为什么不把它记录下来并使用TList<TOffer>呢?


那么,您将如何进行?FastMM 泄漏报告正是您所需要的。您将需要完整的 FastMM 而不是精简的 Embarcadero 版本。它将识别与解除分配不匹配的分配。一一处理。

与此同时,研究高质量的代码。好的开源 Delphi 库将演示上述所有模式,以及更多。向他们学习。

于 2014-04-01T16:07:15.110 回答
2

String由编译器自动管理,您不需要手动释放它(除非在极少数情况下不适用这种情况)。 TObjectList有一个OwnsObjects可以设置为 True 的属性,因此列表会自动为您释放对象。它的构造函数有一个可选AOwnsObjects参数来初始化OwnsObjects属性。

于 2014-04-01T16:09:26.453 回答