Question

My question is about debugging memory leaks which seem to be a nightmare.
In my app there is a simple class derived from TObject. All objects of that class are stored in a collection/list of of the class derived from 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. 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.

Another example is the variable var MyString: string which also needs a proper disposal!! 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?
  • Can you please advise the good reading on fighting memory leaks in Delphi?

Thank you.

Was it helpful?

Solution

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

For an object that is created in a method and only referred to locally, you use the try/finally pattern:

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

2. Object owned by another object

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.

3. Owned TComponent

If a TComponent or a derived class is created with an Owner, then that owner destroys the component. You do not need to.

4. TObjectList or similar with OwnsObjects set to True

You show this pattern in your question. You create a TObjectList<T> and by default OwnsObjects is True. 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

Common examples are objects derived from TInterfacedObject. The interface reference counting manages lifetime. You don't need to destroy the object.

6. Function that creates and returns a new instance

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. 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. However, do note that your implementation of GetOffersList is not exception safe. 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.

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. It has no method and contains three simple scalar values. Why not make it a record and use TList<TOffer>?


So, how do you proceed? The FastMM leak report is what you need. You'll want the full FastMM rather than the cut down Embarcadero version. 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. Learn from them.

OTHER TIPS

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). TObjectList has an OwnsObjects property that you can set to True so the list will free the objects automatically for you. Its constructor has an optional AOwnsObjects parameter to initialize the OwnsObjects property.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top