简体   繁体   English

Delphi app中的内存泄漏。 如何正确处理对象和字符串?

[英]Memory leaks in Delphi app. How to properly dispose objects and strings?

My question is about debugging memory leaks which seem to be a nightmare. 我的问题是关于调试内存泄漏 ,这似乎是一场噩梦。
In my app there is a simple class derived from TObject . 在我的应用程序中有一个从TObject派生的简单类。 All objects of that class are stored in a collection/list of of the class derived from TObjectList : 该类的所有对象都存储在从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;

The usage scenario: 使用场景:
The crawler downloads the offers, parses them and saves to objects collection. 爬虫下载商品,解析商品并保存到对象集合。 This approach seems to be quite convenient as I can refer to the objects later (fill grids/lists, write them to file, etc.) 这种方法似乎很方便,因为我可以稍后引用这些对象(填充网格/列表,将它们写入文件等)

The problem is the proper disposal of the objects to avoid memory leaks. 问题是正确处理对象以避免内存泄漏。 The app allocates ~4Mb memory on start but after processing ~12k offers it devours 32Mb. 该应用程序在启动时分配~4Mb内存,但在处理后~12k提供它吞噬32Mb。 The leaks caused by not properly disposed objects/variables after the process finishes. 在过程完成后由未正确处理的对象/变量引起的泄漏。

ReportMemoryLeaksOnShutdown shows horrible digits, but the crucial is -- I have no idea where to look and how to properly debug the damn thing. ReportMemoryLeaksOnShutdown显示了可怕的数字,但关键是 - 我不知道在哪里看,以及如何正确调试该死的东西。

Another example is the variable var MyString: string which also needs a proper disposal!! 另一个例子是变量var MyString: string ,它也需要妥善处理!! It was sorta insight for me :) I thought each procedure/function automatically manages garbage collection of the out-of-scope variables. 这对我来说很有见地:)我认为每个过程/函数都会自动管理超出范围的变量的垃圾收集。

The list of offers is created by a function: 商品列表由函数创建:

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;

Then I address those offers directly as a collection. 然后我直接将这些优惠作为一个集合来解决。 The key thing is that I want this app to run 24/7, so the correct resource disposal is a must. 关键是我希望这个应用程序全天候运行,因此必须正确处理资源。

  • How to properly dispose object(s) of the above types? 如何妥善处理上述类型的对象?
  • Shall I consider the other techniques to manage object/object lists? 我应该考虑管理对象/对象列表的其他技术吗?
  • How to properly dispose variables of type string ? 如何正确处理string类型的变量?
  • Can you please advise the good reading on fighting memory leaks in Delphi? 你能否告诉Delphi中有关打击内存泄漏的好读物?

Thank you. 谢谢。

By default, when you create an object, you become its owner. 默认情况下,当您创建对象时,您将成为其所有者。 So long as you are the owner, you are responsible for freeing it. 只要您是所有者,您就有责任将其释放。 Here are some of the common patterns: 以下是一些常见模式:

1. Local variable 1.局部变量

For an object that is created in a method and only referred to locally, you use the try/finally pattern: 对于在方法中创建并仅在本地引用的对象,可以使用try / finally模式:

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

2. Object owned by another object 2.另一个对象拥有的对象

Commonly created in the constructor and destroyed in the destructor. 通常在构造函数中创建并在析构函数中销毁。 Here you have a member field of the owning object that holds the reference to the owned object. 这里有一个拥有对象的成员字段,用于保存对拥有对象的引用。 All you need to do is call Free on all owned objects in the owning class destructor. 您需要做的就是在拥有类析构函数中的所有拥有对象上调用Free

3. Owned TComponent 3.拥有的TComponent

If a TComponent or a derived class is created with an Owner , then that owner destroys the component. 如果Owner创建TComponent或派生类,则该所有者将销毁该组件。 You do not need to. 你不需要。

4. TObjectList or similar with OwnsObjects set to True 4. TObjectList或类似的OwnsObjects设置为True

You show this pattern in your question. 您在问题中显示此模式。 You create a TObjectList<T> and by default OwnsObjects is True . 您创建一个TObjectList<T> ,默认情况下OwnsObjectsTrue This means that when you add a member to the container, the container assumes ownership. 这意味着当您向容器添加成员时,容器将承担所有权。 From that point on the container assume responsibility for destroying its members and you do not have to. 从那时起,集装箱承担了销毁其成员的责任,而你不必这样做。 However, somebody still has to destroy the container. 然而,有人仍然需要销毁容器。

5. Reference counted interfaced objects 5.引用计数接口对象

Common examples are objects derived from TInterfacedObject . 常见示例是从TInterfacedObject派生的对象。 The interface reference counting manages lifetime. 接口引用计数管理生命周期。 You don't need to destroy the object. 您不需要销毁该对象。

6. Function that creates and returns a new instance 6.创建并返回新实例的函数

This is towards the more tricky end of the spectrum. 这是针对更棘手的频谱结束。 Thankfully it's a rather rarer pattern. 值得庆幸的是,这是一种相当罕见的模式。 The idea is that the function returns a newly instantiated and initialized object to the caller, who then assumes ownership. 这个想法是该函数将一个新实例化和初始化的对象返回给调用者,然后调用者承担所有权。 But while the function is still executing it is the owner and must defend against exceptions. 但是当函数仍在执行时,它是所有者并且必须防止异常。 Typically the code goes like this: 通常代码如下:

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

This has to be an exception handler with a call to Free and a re-raise because the code is not in a position to use a finally. 这必须是一个异常处理程序,调用Free和re-raise,因为代码不能使用finally。 The caller will do that: 来电者会这样做:

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

Looking at the code in the question, that appears to be using both items 4 and 6 from my list. 查看问题中的代码,似乎使用了我的列表中的第4项和第6项。 However, do note that your implementation of GetOffersList is not exception safe. 但是,请注意您的GetOffersList实现不是异常安全的。 But there's no indication that is the problem. 但没有迹象表明这是问题所在。 It seems plausible that the code that calls GetOffersList is failing to destroy up the container. 调用GetOffersList的代码无法破坏容器似乎是合理的。

Why are you leaking strings? 你为什么要漏字? Well, strings are managed objects. 那么,字符串是托管对象。 They are referenced counted and you need to take no explicit action to destroy them. 它们被引用计数,您不需要采取任何明确的行动来销毁它们。 However, if they are contained in other classes, instances of which are leaked, the contained strings are also leaked. 但是,如果它们包含在其他类中,其实例被泄露,则包含的字符串也会泄露。 So concentrate on fixing the leaks of objects, and you'll take care of the string leaks. 因此,集中精力修复物体的泄漏,你将处理字符串泄漏。

For what it is worth, TOffer feels more like a value type than a reference type to me. 对于它的价值, TOffer对我来说更像是一种价值类型而不是一种参考类型。 It has no method and contains three simple scalar values. 它没有方法,包含三个简单的标量值。 Why not make it a record and use TList<TOffer> ? 为什么不把它作为记录并使用TList<TOffer>


So, how do you proceed? 那么,你如何进行? The FastMM leak report is what you need. FastMM泄漏报告是您所需要的。 You'll want the full FastMM rather than the cut down Embarcadero version. 你需要完整的FastMM而不是减少Embarcadero版本。 It will identify the allocations that were not matched with deallocations. 它将识别与解除分配不匹配的分配。 Deal with them one by one. 一个接一个地处理它们。

In parallel with this, study good quality code. 与此同时,研究高质量的代码。 Good open source Delphi libraries will demonstrate all the patterns above, and many more. 好的开源Delphi库将展示上面的所有模式,以及更多。 Learn from them. 向他们学习。

String is auto-managed by the compiler, you do not need to free it manually (except in rare corner cases that do not apply to this situation). String由编译器自动管理,您不需要手动释放它(除了在不适用于这种情况的罕见极端情况下)。 TObjectList has an OwnsObjects property that you can set to True so the list will free the objects automatically for you. TObjectList具有OwnsObjects属性,您可以将其设置为True,以便列表自动为您释放对象。 Its constructor has an optional AOwnsObjects parameter to initialize the OwnsObjects property. 它的构造函数有一个可选的AOwnsObjects参数来初始化OwnsObjects属性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM